github.com/mgoltzsche/ctnr@v0.7.1-alpha/pkg/lock/lockfile.go (about) 1 package lock 2 3 import ( 4 "os" 5 "path/filepath" 6 "time" 7 8 "github.com/fsnotify/fsnotify" 9 "github.com/nightlyone/lockfile" 10 "github.com/pkg/errors" 11 "github.com/sirupsen/logrus" 12 ) 13 14 type Lockfile struct { 15 file string 16 lockfile lockfile.Lockfile 17 } 18 19 func LockFile(file string) (*Lockfile, error) { 20 file = filepath.Clean(file) 21 l, err := lockfile.New(file) 22 return &Lockfile{file, l}, err 23 } 24 25 func (l *Lockfile) TryLock() (err error) { 26 lock(l.file) 27 28 defer func() { 29 if err != nil { 30 err = errors.Wrap(err, "trylock") 31 unlock(l.file) 32 } 33 }() 34 35 if err = l.mkdirs(); err != nil { 36 return 37 } 38 39 return l.lockfile.TryLock() 40 } 41 42 func (l *Lockfile) mkdirs() error { 43 return errors.Wrap(os.MkdirAll(filepath.Dir(l.file), 0755), "mk lock parent dir") 44 } 45 46 func (l *Lockfile) Lock() (err error) { 47 lock(l.file) 48 49 defer func() { 50 if err != nil { 51 err = errors.Wrap(err, "lock") 52 unlock(l.file) 53 } 54 }() 55 56 if err = l.mkdirs(); err != nil { 57 return 58 } 59 60 for { 61 err = l.lockfile.TryLock() 62 if terr, ok := err.(lockfile.TemporaryError); err == nil || !ok || !terr.Temporary() { 63 // return when locked successfully or error is not temporary 64 return 65 } 66 if err = awaitFileChange(l.file); err != nil && !os.IsNotExist(err) { 67 return 68 } 69 } 70 return 71 } 72 73 func (l *Lockfile) Unlock() (err error) { 74 defer unlock(l.file) 75 if err = l.lockfile.Unlock(); err != nil { 76 err = errors.New("unlock: " + err.Error()) 77 } 78 return 79 } 80 81 func normalizePath(path string) (f string, err error) { 82 if f, err = filepath.EvalSymlinks(path); err != nil { 83 if os.IsNotExist(err) { 84 f, err = normalizePath(filepath.Dir(path)) 85 f = filepath.Join(f, filepath.Base(path)) 86 if err != nil { 87 return 88 } 89 } 90 } 91 if err == nil { 92 f, err = filepath.Abs(f) 93 } 94 if err != nil { 95 err = errors.Errorf("normalize path %q: %s", path, err) 96 } 97 return 98 } 99 100 func awaitFileChange(files ...string) (err error) { 101 if len(files) == 0 { 102 panic("No files provided to watch") 103 } 104 105 watcher, err := fsnotify.NewWatcher() 106 if err != nil { 107 return errors.New(err.Error()) 108 } 109 defer watcher.Close() 110 for _, file := range files { 111 if err = watcher.Add(file); err != nil { 112 return errors.New(err.Error()) 113 } 114 } 115 log := logrus.WithField("files", files) 116 timer := time.NewTimer(5 * time.Second) 117 select { 118 case event := <-watcher.Events: 119 log.Debugln("watch lockfile:", event) 120 return 121 case err = <-watcher.Errors: 122 log.Debugln("watch lockfile:", err) 123 return 124 case <-timer.C: 125 // Timeout to prevent deadlock after other process dies without deleting its lockfile 126 log.Debugln("lockfile watch time expired") 127 return 128 } 129 }