github.com/OpenFlowLabs/storage@v1.12.13/lockfile.go (about) 1 package storage 2 3 import ( 4 "path/filepath" 5 "sync" 6 "time" 7 8 "github.com/pkg/errors" 9 ) 10 11 // A Locker represents a file lock where the file is used to cache an 12 // identifier of the last party that made changes to whatever's being protected 13 // by the lock. 14 type Locker interface { 15 // Acquire a writer lock. 16 Lock() 17 18 // Acquire a writer lock recursively, allowing for recursive acquisitions 19 // within the same process space. 20 RecursiveLock() 21 22 // Unlock the lock. 23 Unlock() 24 25 // Acquire a reader lock. 26 RLock() 27 28 // Touch records, for others sharing the lock, that the caller was the 29 // last writer. It should only be called with the lock held. 30 Touch() error 31 32 // Modified() checks if the most recent writer was a party other than the 33 // last recorded writer. It should only be called with the lock held. 34 Modified() (bool, error) 35 36 // TouchedSince() checks if the most recent writer modified the file (likely using Touch()) after the specified time. 37 TouchedSince(when time.Time) bool 38 39 // IsReadWrite() checks if the lock file is read-write 40 IsReadWrite() bool 41 42 // Locked() checks if lock is locked for writing by a thread in this process 43 Locked() bool 44 } 45 46 var ( 47 lockfiles map[string]Locker 48 lockfilesLock sync.Mutex 49 ) 50 51 // GetLockfile opens a read-write lock file, creating it if necessary. The 52 // Locker object may already be locked if the path has already been requested 53 // by the current process. 54 func GetLockfile(path string) (Locker, error) { 55 return getLockfile(path, false) 56 } 57 58 // GetROLockfile opens a read-only lock file, creating it if necessary. The 59 // Locker object may already be locked if the path has already been requested 60 // by the current process. 61 func GetROLockfile(path string) (Locker, error) { 62 return getLockfile(path, true) 63 } 64 65 // getLockfile returns a Locker object, possibly (depending on the platform) 66 // working inter-process, and associated with the specified path. 67 // 68 // If ro, the lock is a read-write lock and the returned Locker should correspond to the 69 // “lock for reading” (shared) operation; otherwise, the lock is either an exclusive lock, 70 // or a read-write lock and Locker should correspond to the “lock for writing” (exclusive) operation. 71 // 72 // WARNING: 73 // - The lock may or MAY NOT be inter-process. 74 // - There may or MAY NOT be an actual object on the filesystem created for the specified path. 75 // - Even if ro, the lock MAY be exclusive. 76 func getLockfile(path string, ro bool) (Locker, error) { 77 lockfilesLock.Lock() 78 defer lockfilesLock.Unlock() 79 if lockfiles == nil { 80 lockfiles = make(map[string]Locker) 81 } 82 cleanPath, err := filepath.Abs(path) 83 if err != nil { 84 return nil, errors.Wrapf(err, "error ensuring that path %q is an absolute path", path) 85 } 86 if locker, ok := lockfiles[cleanPath]; ok { 87 if ro && locker.IsReadWrite() { 88 return nil, errors.Errorf("lock %q is not a read-only lock", cleanPath) 89 } 90 if !ro && !locker.IsReadWrite() { 91 return nil, errors.Errorf("lock %q is not a read-write lock", cleanPath) 92 } 93 return locker, nil 94 } 95 locker, err := createLockerForPath(path, ro) // platform-dependent locker 96 if err != nil { 97 return nil, err 98 } 99 lockfiles[filepath.Clean(path)] = locker 100 return locker, nil 101 }