github.com/zhyoulun/cilium@v1.6.12/pkg/kvstore/lock.go (about) 1 // Copyright 2016-2019 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kvstore 16 17 import ( 18 "context" 19 "fmt" 20 "time" 21 22 "github.com/cilium/cilium/pkg/debug" 23 "github.com/cilium/cilium/pkg/defaults" 24 "github.com/cilium/cilium/pkg/lock" 25 uuidfactor "github.com/cilium/cilium/pkg/uuid" 26 27 "github.com/davecgh/go-spew/spew" 28 "github.com/pborman/uuid" 29 "github.com/sirupsen/logrus" 30 ) 31 32 var ( 33 kvstoreLocks = pathLocks{lockPaths: map[string]lockOwner{}} 34 35 // staleLockTimeout is the timeout after which waiting for a believed 36 // other local lock user for the same key is given up on and etcd is 37 // asked directly. It is still highly unlikely that concurrent access 38 // occurs as only one consumer will manage to acquire the newly 39 // released lock. The only possibility of concurrent access is if a 40 // consumer is *still* holding the lock but this is highly unlikely 41 // given the duration of this timeout. 42 staleLockTimeout = defaults.KVStoreStaleLockTimeout 43 ) 44 45 type KVLocker interface { 46 Unlock() error 47 // Comparator returns an object that should be used by the KVStore to make 48 // sure if the lock is still valid for its client or nil if no such 49 // verification exists. 50 Comparator() interface{} 51 } 52 53 // getLockPath returns the lock path representation of the given path. 54 func getLockPath(path string) string { 55 return path + ".lock" 56 } 57 58 type lockOwner struct { 59 created time.Time 60 id uuid.UUID 61 } 62 63 type pathLocks struct { 64 mutex lock.RWMutex 65 lockPaths map[string]lockOwner 66 } 67 68 func init() { 69 debug.RegisterStatusObject("kvstore-locks", &kvstoreLocks) 70 } 71 72 // DebugStatus implements debug.StatusObject to provide debug status collection 73 // ability 74 func (pl *pathLocks) DebugStatus() string { 75 pl.mutex.RLock() 76 str := spew.Sdump(pl.lockPaths) 77 pl.mutex.RUnlock() 78 return str 79 } 80 81 func (pl *pathLocks) runGC() { 82 pl.mutex.Lock() 83 for path, owner := range pl.lockPaths { 84 if time.Since(owner.created) > staleLockTimeout { 85 log.WithField("path", path).Error("Forcefully unlocking local kvstore lock") 86 delete(pl.lockPaths, path) 87 } 88 } 89 pl.mutex.Unlock() 90 } 91 92 func (pl *pathLocks) lock(ctx context.Context, path string) (id uuid.UUID, err error) { 93 for { 94 pl.mutex.Lock() 95 if _, ok := pl.lockPaths[path]; !ok { 96 id = uuidfactor.NewUUID() 97 pl.lockPaths[path] = lockOwner{ 98 created: time.Now(), 99 id: id, 100 } 101 pl.mutex.Unlock() 102 return 103 } 104 pl.mutex.Unlock() 105 106 select { 107 case <-time.After(time.Duration(10) * time.Millisecond): 108 case <-ctx.Done(): 109 err = fmt.Errorf("lock was cancelled: %s", ctx.Err()) 110 return 111 } 112 } 113 } 114 115 func (pl *pathLocks) unlock(path string, id uuid.UUID) { 116 pl.mutex.Lock() 117 if owner, ok := pl.lockPaths[path]; ok && uuid.Equal(owner.id, id) { 118 delete(pl.lockPaths, path) 119 } 120 pl.mutex.Unlock() 121 } 122 123 // Lock is a lock return by LockPath 124 type Lock struct { 125 path string 126 id uuid.UUID 127 kvLock KVLocker 128 } 129 130 // LockPath locks the specified path. The key for the lock is not the path 131 // provided itself but the path with a suffix of ".lock" appended. The lock 132 // returned also contains a patch specific local Mutex which will be held. 133 // 134 // It is required to call Unlock() on the returned Lock to unlock 135 func LockPath(ctx context.Context, backend BackendOperations, path string) (l *Lock, err error) { 136 id, err := kvstoreLocks.lock(ctx, path) 137 if err != nil { 138 return nil, err 139 } 140 141 lock, err := backend.LockPath(ctx, path) 142 if err != nil { 143 kvstoreLocks.unlock(path, id) 144 Trace("Failed to lock", err, logrus.Fields{fieldKey: path}) 145 err = fmt.Errorf("error while locking path %s: %s", path, err) 146 return nil, err 147 } 148 149 Trace("Successful lock", err, logrus.Fields{fieldKey: path}) 150 return &Lock{kvLock: lock, path: path, id: id}, err 151 } 152 153 // RunLockGC inspects all local kvstore locks to determine whether they have 154 // been held longer than the stale lock timeout, and if so, unlocks them 155 // forceably. 156 func RunLockGC() { 157 kvstoreLocks.runGC() 158 } 159 160 // Unlock unlocks a lock 161 func (l *Lock) Unlock() error { 162 if l == nil { 163 return nil 164 } 165 166 // Unlock kvstore mutex first 167 err := l.kvLock.Unlock() 168 if err != nil { 169 log.WithError(err).WithField("path", l.path).Error("Unable to unlock kvstore lock") 170 } 171 172 // unlock local lock even if kvstore cannot be unlocked 173 kvstoreLocks.unlock(l.path, l.id) 174 Trace("Unlocked", nil, logrus.Fields{fieldKey: l.path}) 175 176 return err 177 } 178 179 func (l *Lock) Comparator() interface{} { 180 return l.kvLock.Comparator() 181 }