github.com/cs3org/reva/v2@v2.27.7/pkg/storage/utils/filelocks/filelocks.go (about)

     1  // Copyright 2018-2021 CERN
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  // In applying this license, CERN does not waive the privileges and immunities
    16  // granted to it by virtue of its status as an Intergovernmental Organization
    17  // or submit itself to any jurisdiction.
    18  
    19  package filelocks
    20  
    21  import (
    22  	"errors"
    23  	"io/fs"
    24  	"os"
    25  	"sync"
    26  	"time"
    27  
    28  	"github.com/gofrs/flock"
    29  )
    30  
    31  // LockFileSuffix to use for lock files
    32  const LockFileSuffix = ".flock"
    33  
    34  var (
    35  	_localLocks sync.Map
    36  	// waiting 20 lock cycles with a factor of 30 yields 6300ms, or a little over 6 sec
    37  	_lockCycles              sync.Once
    38  	_lockCyclesValue         = 20
    39  	_lockCycleDuration       sync.Once
    40  	_lockCycleDurationFactor = 30
    41  
    42  	// ErrPathEmpty indicates that no path was specified
    43  	ErrPathEmpty = errors.New("lock path is empty")
    44  	// ErrAcquireLockFailed indicates that it was not possible to lock the resource.
    45  	ErrAcquireLockFailed = errors.New("unable to acquire a lock on the file")
    46  )
    47  
    48  // SetMaxLockCycles configures the maximum amount of lock cycles. Subsequent calls to SetMaxLockCycles have no effect
    49  func SetMaxLockCycles(v int) {
    50  	_lockCycles.Do(func() {
    51  		_lockCyclesValue = v
    52  	})
    53  }
    54  
    55  // SetLockCycleDurationFactor configures the factor applied to the timeout allowed during a lock cycle. Subsequent calls to SetLockCycleDurationFactor have no effect
    56  func SetLockCycleDurationFactor(v int) {
    57  	_lockCycleDuration.Do(func() {
    58  		_lockCycleDurationFactor = v
    59  	})
    60  }
    61  
    62  // getMutexedFlock returns a new Flock struct for the given file.
    63  // If there is already one in the local store, it returns nil.
    64  // The caller has to wait until it can get a new one out of this
    65  // mehtod.
    66  func getMutexedFlock(file string) *flock.Flock {
    67  
    68  	// Is there lock already?
    69  	if _, ok := _localLocks.Load(file); ok {
    70  		// There is already a lock for this file, another can not be acquired
    71  		return nil
    72  	}
    73  
    74  	// Acquire the write log on the target node first.
    75  	l := flock.New(file)
    76  	_localLocks.Store(file, l)
    77  	return l
    78  
    79  }
    80  
    81  // releaseMutexedFlock releases a Flock object that was acquired
    82  // before by the getMutexedFlock function.
    83  func releaseMutexedFlock(file string) {
    84  	if len(file) > 0 {
    85  		_localLocks.Delete(file)
    86  	}
    87  }
    88  
    89  // acquireWriteLog acquires a lock on a file or directory.
    90  // if the parameter write is true, it gets an exclusive write lock, otherwise a shared read lock.
    91  // The function returns a Flock object, unlocking has to be done in the calling function.
    92  func acquireLock(file string, write bool) (*flock.Flock, error) {
    93  	var err error
    94  
    95  	// Create a file to carry the log
    96  	n := FlockFile(file)
    97  	if len(n) == 0 {
    98  		return nil, ErrPathEmpty
    99  	}
   100  
   101  	var flock *flock.Flock
   102  	for i := 1; i <= _lockCyclesValue; i++ {
   103  		if flock = getMutexedFlock(n); flock != nil {
   104  			break
   105  		}
   106  		w := time.Duration(i*_lockCycleDurationFactor) * time.Millisecond
   107  
   108  		time.Sleep(w)
   109  	}
   110  	if flock == nil {
   111  		return nil, ErrAcquireLockFailed
   112  	}
   113  
   114  	var ok bool
   115  	for i := 1; i <= _lockCyclesValue; i++ {
   116  		if write {
   117  			ok, err = flock.TryLock()
   118  		} else {
   119  			ok, err = flock.TryRLock()
   120  		}
   121  
   122  		if ok {
   123  			break
   124  		}
   125  
   126  		time.Sleep(time.Duration(i*_lockCycleDurationFactor) * time.Millisecond)
   127  	}
   128  
   129  	if !ok {
   130  		err = ErrAcquireLockFailed
   131  	}
   132  
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  	return flock, nil
   137  }
   138  
   139  // FlockFile returns the flock filename for a given file name
   140  // it returns an empty string if the input is empty
   141  func FlockFile(file string) string {
   142  	if file == "" {
   143  		return ""
   144  	}
   145  	return file + LockFileSuffix
   146  }
   147  
   148  // AcquireReadLock tries to acquire a shared lock to read from the
   149  // file and returns a lock object or an error accordingly.
   150  // Call with the file to lock. This function creates .lock file next
   151  // to it.
   152  func AcquireReadLock(file string) (*flock.Flock, error) {
   153  	return acquireLock(file, false)
   154  }
   155  
   156  // AcquireWriteLock tries to acquire a shared lock to write from the
   157  // file and returns a lock object or an error accordingly.
   158  // Call with the file to lock. This function creates an extra .lock
   159  // file next to it.
   160  func AcquireWriteLock(file string) (*flock.Flock, error) {
   161  	return acquireLock(file, true)
   162  }
   163  
   164  // ReleaseLock releases a lock from a file that was previously created
   165  // by AcquireReadLock or AcquireWriteLock.
   166  func ReleaseLock(lock *flock.Flock) error {
   167  	if lock == nil {
   168  		return errors.New("cannot unlock nil lock")
   169  	}
   170  
   171  	// there is a probability that if the file can not be unlocked,
   172  	// we also can not remove the file. We will only try to remove if it
   173  	// was successfully unlocked.
   174  	var err error
   175  	n := lock.Path()
   176  	// There is already a lock for this file
   177  
   178  	err = lock.Unlock()
   179  	if err == nil {
   180  		if !lock.Locked() && !lock.RLocked() {
   181  			err = os.Remove(n)
   182  			// there is a concurrency issue when deleting the file
   183  			// see https://github.com/owncloud/ocis/issues/3757
   184  			// for now we just ignore "not found" errors when they pop up
   185  			if err != nil && errors.Is(err, fs.ErrNotExist) {
   186  				err = nil
   187  			}
   188  		}
   189  	}
   190  	releaseMutexedFlock(n)
   191  
   192  	return err
   193  }