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  }