github.com/mgoltzsche/ctnr@v0.7.1-alpha/pkg/lock/sharedlockdir.go (about) 1 package lock 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "path/filepath" 8 "strconv" 9 "strings" 10 "syscall" 11 12 "github.com/pkg/errors" 13 ) 14 15 // TODO: Make sure sharedLockDir is thread-safe. 16 // Currently this is not the case since Lock() can be called concurrently while changing the sharedLockFile property this results in an inconsistent state 17 // => already fixed by having sharedDirLock acquire lock at construction time again 18 19 type exclusiveLocker struct { 20 lockfile *Lockfile 21 dir string 22 } 23 24 /*type noopLocker string 25 26 func NewNoopLocker() Locker { 27 return noopLocker("nooplocker") 28 } 29 30 func (l *NoopLocker) Locker { 31 32 }*/ 33 34 func NewExclusiveDirLocker(dir string) (r ExclusiveLocker, err error) { 35 l := exclusiveLocker{} 36 if l.lockfile, err = LockFile(filepath.Join(dir, ".exclusive.lock")); err != nil { 37 return 38 } 39 l.dir = dir 40 return &l, err 41 } 42 43 func (l *exclusiveLocker) mkdir() (err error) { 44 err = os.MkdirAll(l.dir, 0755) 45 return errors.Wrap(err, "init lock directory") 46 } 47 48 func (l *exclusiveLocker) NewSharedLocker() Locker { 49 return &sharedLocker{"", l.dir, l.lockfile} 50 } 51 52 func (l *exclusiveLocker) Lock() (err error) { 53 if err = l.mkdir(); err != nil { 54 return 55 } 56 if err = l.lockfile.Lock(); err != nil { 57 return 58 } 59 defer func() { 60 if err != nil { 61 if e := l.Unlock(); e != nil { 62 // TODO: split error properly 63 err = errors.Errorf("%s, lock: %s", err, e) 64 } 65 } 66 }() 67 68 // Wait until no shared lock acquired 69 return errors.Wrap(l.awaitSharedLocks(), "lock") 70 } 71 72 func (l *exclusiveLocker) Unlock() error { 73 return errors.Wrap(l.lockfile.Unlock(), "unlock") 74 } 75 76 func (l *exclusiveLocker) awaitSharedLocks() (err error) { 77 locked := true 78 var fl []os.FileInfo 79 for locked { 80 fl, err = ioutil.ReadDir(l.dir) 81 if err != nil { 82 return 83 } 84 locked = false 85 for _, f := range fl { 86 if f.IsDir() { 87 continue 88 } 89 fname := f.Name() 90 fpath := filepath.Join(l.dir, fname) 91 ns := strings.SplitN(fname, "-", 3) 92 if len(ns) != 3 || ns[2] == "" { 93 continue 94 } 95 pid, e := strconv.Atoi(ns[1]) 96 if e != nil || pid < 1 { 97 // ignore non shared lock files + own lock file 98 continue 99 } 100 p, e := os.FindProcess(pid) 101 if e != nil || p.Signal(syscall.Signal(0)) != nil { 102 // Ignore and remove file from not existing process 103 //TODO: os.Remove(fpath) 104 continue 105 } 106 locked = true 107 if e = awaitFileChange(fpath, l.dir); e != nil && !os.IsNotExist(e) { 108 err = errors.Wrap(e, "await exclusive lock usage") 109 return 110 } 111 break 112 } 113 } 114 return 115 } 116 117 type sharedLocker struct { 118 sharedLockFile string 119 dir string 120 exclusive *Lockfile 121 } 122 123 func (l *sharedLocker) Lock() (err error) { 124 if l.sharedLockFile != "" { 125 panic("lock shared: shared lock is already locked: " + l.sharedLockFile) 126 } 127 128 // Lock dir exclusively 129 err = l.exclusive.Lock() 130 if err != nil { 131 err = errors.Wrap(err, "shared lock") 132 return 133 } 134 defer l.exclusive.Unlock() 135 136 // Register shared lock file 137 file, err := ioutil.TempFile(l.dir, fmt.Sprintf(".sharedlock-%d-", os.Getpid())) 138 if err != nil { 139 err = errors.Wrap(err, "shared lock") 140 return 141 } 142 l.sharedLockFile = file.Name() 143 file.Close() 144 return 145 } 146 147 func (l *sharedLocker) Unlock() (err error) { 148 if l.sharedLockFile == "" { 149 // If this happens there is some serious misusage of this package 150 // happening which can lead to further errors due to inconsistency. 151 panic("unlock shared: invalid state - was not locked") 152 } 153 if err = os.Remove(l.sharedLockFile); err != nil { 154 err = errors.Wrap(err, "unlock shared") 155 } 156 l.sharedLockFile = "" 157 return 158 }