github.com/blixtra/rkt@v0.8.1-0.20160204105720-ab0d1add1a43/pkg/lock/keylock.go (about) 1 // Copyright 2015 The rkt Authors 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 lock 16 17 import ( 18 "errors" 19 "fmt" 20 "os" 21 "path/filepath" 22 "syscall" 23 24 "github.com/hashicorp/errwrap" 25 ) 26 27 const ( 28 defaultDirPerm os.FileMode = 0660 29 defaultFilePerm os.FileMode = 0660 30 defaultLockRetries = 3 31 ) 32 33 type keyLockMode uint 34 35 const ( 36 keyLockExclusive keyLockMode = 1 << iota 37 keyLockShared 38 keyLockNonBlocking 39 ) 40 41 // KeyLock is a lock for a specific key. The lock file is created inside a 42 // directory using the key name. 43 // This is useful when multiple processes want to take a lock but cannot use 44 // FileLock as they don't have a well defined file on the filesystem. 45 // key value must be a valid file name (as the lock file is named after the key 46 // value). 47 type KeyLock struct { 48 lockDir string 49 key string 50 // The lock on the key 51 keyLock *FileLock 52 } 53 54 // NewKeyLock returns a KeyLock for the specified key without acquisition. 55 // lockdir is the directory where the lock file will be created. If lockdir 56 // doesn't exists it will be created. 57 // key value must be a valid file name (as the lock file is named after the key 58 // value). 59 func NewKeyLock(lockDir string, key string) (*KeyLock, error) { 60 err := os.MkdirAll(lockDir, defaultDirPerm) 61 if err != nil { 62 return nil, err 63 } 64 keyLockFile := filepath.Join(lockDir, key) 65 // create the file if it doesn't exists 66 f, err := os.OpenFile(keyLockFile, os.O_RDONLY|os.O_CREATE, defaultFilePerm) 67 if err != nil { 68 return nil, errwrap.Wrap(errors.New("error creating key lock file"), err) 69 } 70 f.Close() 71 keyLock, err := NewLock(keyLockFile, RegFile) 72 if err != nil { 73 return nil, errwrap.Wrap(errors.New("error opening key lock file"), err) 74 } 75 return &KeyLock{lockDir: lockDir, key: key, keyLock: keyLock}, nil 76 } 77 78 // Close closes the key lock which implicitly unlocks it as well 79 func (l *KeyLock) Close() { 80 l.keyLock.Close() 81 } 82 83 // TryExclusiveLock takes an exclusive lock on a key without blocking. 84 // This is idempotent when the KeyLock already represents an exclusive lock, 85 // and tries promote a shared lock to exclusive atomically. 86 // It will return ErrLocked if any lock is already held on the key. 87 func (l *KeyLock) TryExclusiveKeyLock() error { 88 return l.lock(keyLockExclusive|keyLockNonBlocking, defaultLockRetries) 89 } 90 91 // TryExclusiveLock takes an exclusive lock on the key without blocking. 92 // lockDir is the directory where the lock file will be created. 93 // It will return ErrLocked if any lock is already held. 94 func TryExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) { 95 return createAndLock(lockDir, key, keyLockExclusive|keyLockNonBlocking) 96 } 97 98 // ExclusiveLock takes an exclusive lock on a key. 99 // This is idempotent when the KeyLock already represents an exclusive lock, 100 // and promotes a shared lock to exclusive atomically. 101 // It will block if an exclusive lock is already held on the key. 102 func (l *KeyLock) ExclusiveKeyLock() error { 103 return l.lock(keyLockExclusive, defaultLockRetries) 104 } 105 106 // ExclusiveLock takes an exclusive lock on a key. 107 // lockDir is the directory where the lock file will be created. 108 // It will block if an exclusive lock is already held on the key. 109 func ExclusiveKeyLock(lockDir string, key string) (*KeyLock, error) { 110 return createAndLock(lockDir, key, keyLockExclusive) 111 } 112 113 // TrySharedLock takes a co-operative (shared) lock on the key without blocking. 114 // This is idempotent when the KeyLock already represents a shared lock, 115 // and tries demote an exclusive lock to shared atomically. 116 // It will return ErrLocked if an exclusive lock already exists on the key. 117 func (l *KeyLock) TrySharedKeyLock() error { 118 return l.lock(keyLockShared|keyLockNonBlocking, defaultLockRetries) 119 } 120 121 // TrySharedLock takes a co-operative (shared) lock on a key without blocking. 122 // lockDir is the directory where the lock file will be created. 123 // It will return ErrLocked if an exclusive lock already exists on the key. 124 func TrySharedKeyLock(lockDir string, key string) (*KeyLock, error) { 125 return createAndLock(lockDir, key, keyLockShared|keyLockNonBlocking) 126 } 127 128 // SharedLock takes a co-operative (shared) lock on a key. 129 // This is idempotent when the KeyLock already represents a shared lock, 130 // and demotes an exclusive lock to shared atomically. 131 // It will block if an exclusive lock is already held on the key. 132 func (l *KeyLock) SharedKeyLock() error { 133 return l.lock(keyLockShared, defaultLockRetries) 134 } 135 136 // SharedLock takes a co-operative (shared) lock on a key. 137 // lockDir is the directory where the lock file will be created. 138 // It will block if an exclusive lock is already held on the key. 139 func SharedKeyLock(lockDir string, key string) (*KeyLock, error) { 140 return createAndLock(lockDir, key, keyLockShared) 141 } 142 143 func createAndLock(lockDir string, key string, mode keyLockMode) (*KeyLock, error) { 144 keyLock, err := NewKeyLock(lockDir, key) 145 if err != nil { 146 return nil, err 147 } 148 err = keyLock.lock(mode, defaultLockRetries) 149 if err != nil { 150 keyLock.Close() 151 return nil, err 152 } 153 return keyLock, nil 154 } 155 156 // lock is the base function to take a lock and handle changed lock files 157 // As there's the need to remove unused (see CleanKeyLocks) lock files without 158 // races, a changed file detection is needed. 159 // 160 // Without changed file detection this can happen: 161 // 162 // Process A takes exclusive lock on file01 163 // Process B waits for exclusive lock on file01. 164 // Process A deletes file01 and then releases the lock. 165 // Process B takes the lock on the removed file01 as it has the fd opened 166 // Process C comes, creates the file as it doesn't exists, and it also takes an exclusive lock. 167 // Now B and C thinks to own an exclusive lock. 168 // 169 // maxRetries can be passed, useful for testing. 170 func (l *KeyLock) lock(mode keyLockMode, maxRetries int) error { 171 retries := 0 172 for { 173 var err error 174 var isExclusive bool 175 var isNonBlocking bool 176 if mode&keyLockExclusive != 0 { 177 isExclusive = true 178 } 179 if mode&keyLockNonBlocking != 0 { 180 isNonBlocking = true 181 } 182 switch { 183 case isExclusive && !isNonBlocking: 184 err = l.keyLock.ExclusiveLock() 185 case isExclusive && isNonBlocking: 186 err = l.keyLock.TryExclusiveLock() 187 case !isExclusive && !isNonBlocking: 188 err = l.keyLock.SharedLock() 189 case !isExclusive && isNonBlocking: 190 err = l.keyLock.TrySharedLock() 191 } 192 if err != nil { 193 return err 194 } 195 196 // Check that the file referenced by the lock fd is the same as 197 // the current file on the filesystem 198 var lockStat, curStat syscall.Stat_t 199 lfd, err := l.keyLock.Fd() 200 if err != nil { 201 return err 202 } 203 err = syscall.Fstat(lfd, &lockStat) 204 if err != nil { 205 return err 206 } 207 keyLockFile := filepath.Join(l.lockDir, l.key) 208 fd, err := syscall.Open(keyLockFile, syscall.O_RDONLY, 0) 209 // If there's an error opening the file return an error 210 if err != nil { 211 return err 212 } 213 if err := syscall.Fstat(fd, &curStat); err != nil { 214 syscall.Close(fd) 215 return err 216 } 217 syscall.Close(fd) 218 if lockStat.Ino == curStat.Ino && lockStat.Dev == curStat.Dev { 219 return nil 220 } 221 if retries >= maxRetries { 222 return fmt.Errorf("cannot acquire lock after %d retries", retries) 223 } 224 225 // If the file has changed discard this lock and try to take another lock. 226 l.keyLock.Close() 227 nl, err := NewKeyLock(l.lockDir, l.key) 228 if err != nil { 229 return err 230 } 231 l.keyLock = nl.keyLock 232 233 retries++ 234 } 235 } 236 237 // Unlock unlocks the key lock. 238 func (l *KeyLock) Unlock() error { 239 err := l.keyLock.Unlock() 240 if err != nil { 241 return err 242 } 243 return nil 244 } 245 246 // CleanKeyLocks remove lock files from the lockDir. 247 // For every key it tries to take an Exclusive lock on it and skip it if it 248 // fails with ErrLocked 249 func CleanKeyLocks(lockDir string) error { 250 f, err := os.Open(lockDir) 251 if err != nil { 252 return errwrap.Wrap(errors.New("error opening lockDir"), err) 253 } 254 defer f.Close() 255 files, err := f.Readdir(0) 256 if err != nil { 257 return errwrap.Wrap(errors.New("error getting lock files list"), err) 258 } 259 for _, f := range files { 260 filename := filepath.Join(lockDir, f.Name()) 261 keyLock, err := TryExclusiveKeyLock(lockDir, f.Name()) 262 if err == ErrLocked { 263 continue 264 } 265 if err != nil { 266 return err 267 } 268 269 err = os.Remove(filename) 270 if err != nil { 271 keyLock.Close() 272 return errwrap.Wrap(errors.New("error removing lock file"), err) 273 } 274 keyLock.Close() 275 } 276 return nil 277 }