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 }