github.com/cyverse/go-irodsclient@v0.13.2/fs/file_lock.go (about)

     1  package fs
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"sync"
     7  
     8  	"golang.org/x/xerrors"
     9  )
    10  
    11  // this is a file lock managed by go-irodsclient. Not same as fs file lock
    12  
    13  // FileLock is a lock for a file
    14  type FileLock struct {
    15  	path       string
    16  	references int64
    17  	mutex      sync.RWMutex // used to lock the file
    18  }
    19  
    20  // FileLocks manages file locks
    21  type FileLocks struct {
    22  	mutex sync.Mutex
    23  	locks map[string]*FileLock
    24  }
    25  
    26  // NewFileLocks creates a new FileLocks
    27  func NewFileLocks() *FileLocks {
    28  	return &FileLocks{
    29  		mutex: sync.Mutex{},
    30  		locks: map[string]*FileLock{},
    31  	}
    32  }
    33  
    34  // LockFilesForPrefix locks all files starting with the given prefix, does not create a new lock, but increases reference
    35  func (mgr *FileLocks) LockFilesForPrefix(pathPrefix string) []string {
    36  	fileLocks := []*FileLock{}
    37  
    38  	mgr.mutex.Lock()
    39  
    40  	prefix := fmt.Sprintf("%s/", pathPrefix)
    41  	for _, lock := range mgr.locks {
    42  		if strings.HasPrefix(lock.path, prefix) {
    43  			fileLocks = append(fileLocks, lock)
    44  			lock.references++
    45  		}
    46  	}
    47  
    48  	mgr.mutex.Unlock()
    49  
    50  	lockedFilePaths := []string{}
    51  	for _, fileLock := range fileLocks {
    52  		fileLock.mutex.Lock() // write lock
    53  		lockedFilePaths = append(lockedFilePaths, fileLock.path)
    54  	}
    55  
    56  	return lockedFilePaths
    57  }
    58  
    59  // UnlockFiles unlocks multiple files
    60  func (mgr *FileLocks) UnlockFiles(paths []string) error {
    61  	fileLocks := []*FileLock{}
    62  
    63  	mgr.mutex.Lock()
    64  
    65  	for _, path := range paths {
    66  		if lock, ok := mgr.locks[path]; ok {
    67  			// fileLock already exists
    68  			fileLocks = append(fileLocks, lock)
    69  
    70  			if lock.references <= 0 {
    71  				mgr.mutex.Unlock()
    72  				return xerrors.Errorf("file lock for path %s has invalid references %d", path, lock.references)
    73  			}
    74  
    75  			lock.references--
    76  
    77  			if lock.references == 0 {
    78  				delete(mgr.locks, path)
    79  			}
    80  		} else {
    81  			mgr.mutex.Unlock()
    82  			return xerrors.Errorf("file lock for path %s does not exist", path)
    83  		}
    84  	}
    85  
    86  	mgr.mutex.Unlock()
    87  
    88  	// unlock in reverse order
    89  	for i := len(fileLocks) - 1; i >= 0; i-- {
    90  		fileLock := fileLocks[i]
    91  		fileLock.mutex.Unlock() // unlock write lock
    92  	}
    93  
    94  	return nil
    95  }
    96  
    97  // Lock locks a file
    98  func (mgr *FileLocks) Lock(path string) {
    99  	var fileLock *FileLock
   100  
   101  	mgr.mutex.Lock()
   102  
   103  	if lock, ok := mgr.locks[path]; ok {
   104  		// fileLock already exists
   105  		fileLock = lock
   106  		fileLock.references++
   107  	} else {
   108  		// create a new
   109  		fileLock = &FileLock{
   110  			path:       path,
   111  			references: 1,
   112  			mutex:      sync.RWMutex{},
   113  		}
   114  		mgr.locks[path] = fileLock
   115  	}
   116  
   117  	mgr.mutex.Unlock()
   118  
   119  	fileLock.mutex.Lock() // write lock
   120  }
   121  
   122  // RLock locks a file with read mode
   123  func (mgr *FileLocks) RLock(path string) {
   124  	var fileLock *FileLock
   125  
   126  	mgr.mutex.Lock()
   127  
   128  	if lock, ok := mgr.locks[path]; ok {
   129  		// fileLock already exists
   130  		fileLock = lock
   131  		fileLock.references++
   132  	} else {
   133  		// create a new
   134  		fileLock = &FileLock{
   135  			path:       path,
   136  			references: 1,
   137  			mutex:      sync.RWMutex{},
   138  		}
   139  		mgr.locks[path] = fileLock
   140  	}
   141  
   142  	mgr.mutex.Unlock()
   143  
   144  	fileLock.mutex.RLock() // read lock
   145  }
   146  
   147  // Unlock unlocks a file
   148  func (mgr *FileLocks) Unlock(path string) error {
   149  	var fileLock *FileLock
   150  
   151  	mgr.mutex.Lock()
   152  
   153  	if lock, ok := mgr.locks[path]; ok {
   154  		// fileLock already exists
   155  		fileLock = lock
   156  	} else {
   157  		mgr.mutex.Unlock()
   158  		return xerrors.Errorf("file lock for path %s does not exist", path)
   159  	}
   160  
   161  	if fileLock.references <= 0 {
   162  		mgr.mutex.Unlock()
   163  		return xerrors.Errorf("file lock for path %s has invalid references %d", path, fileLock.references)
   164  	}
   165  
   166  	fileLock.references--
   167  
   168  	if fileLock.references == 0 {
   169  		delete(mgr.locks, path)
   170  	}
   171  
   172  	mgr.mutex.Unlock()
   173  
   174  	fileLock.mutex.Unlock()
   175  	return nil
   176  }
   177  
   178  // RUnlock unlocks a file with read mode
   179  func (mgr *FileLocks) RUnlock(path string) error {
   180  	var fileLock *FileLock
   181  
   182  	mgr.mutex.Lock()
   183  
   184  	if lock, ok := mgr.locks[path]; ok {
   185  		// fileLock already exists
   186  		fileLock = lock
   187  	} else {
   188  		mgr.mutex.Unlock()
   189  		return xerrors.Errorf("file lock for path %s does not exist", path)
   190  	}
   191  
   192  	if fileLock.references <= 0 {
   193  		mgr.mutex.Unlock()
   194  		return xerrors.Errorf("file lock for path %s has invalid references %d", path, fileLock.references)
   195  	}
   196  
   197  	fileLock.references--
   198  
   199  	if fileLock.references == 0 {
   200  		delete(mgr.locks, path)
   201  	}
   202  
   203  	mgr.mutex.Unlock()
   204  
   205  	fileLock.mutex.RUnlock()
   206  	return nil
   207  }