github.com/sshnaidm/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  }