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  }