storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/lsync/lrwmutex.go (about) 1 /* 2 * Minio Cloud Storage, (C) 2017 Minio, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package lsync 18 19 import ( 20 "context" 21 "math" 22 "math/rand" 23 "sync" 24 "time" 25 ) 26 27 // A LRWMutex is a mutual exclusion lock with timeouts. 28 type LRWMutex struct { 29 id string 30 source string 31 isWriteLock bool 32 ref int 33 mu sync.Mutex // Mutex to prevent multiple simultaneous locks 34 } 35 36 // NewLRWMutex - initializes a new lsync RW mutex. 37 func NewLRWMutex() *LRWMutex { 38 return &LRWMutex{} 39 } 40 41 // Lock holds a write lock on lm. 42 // 43 // If the lock is already in use, the calling go routine 44 // blocks until the mutex is available. 45 func (lm *LRWMutex) Lock() { 46 47 const isWriteLock = true 48 lm.lockLoop(context.Background(), lm.id, lm.source, math.MaxInt64, isWriteLock) 49 } 50 51 // GetLock tries to get a write lock on lm before the timeout occurs. 52 func (lm *LRWMutex) GetLock(ctx context.Context, id string, source string, timeout time.Duration) (locked bool) { 53 54 const isWriteLock = true 55 return lm.lockLoop(ctx, id, source, timeout, isWriteLock) 56 } 57 58 // RLock holds a read lock on lm. 59 // 60 // If one or more read lock are already in use, it will grant another lock. 61 // Otherwise the calling go routine blocks until the mutex is available. 62 func (lm *LRWMutex) RLock() { 63 64 const isWriteLock = false 65 lm.lockLoop(context.Background(), lm.id, lm.source, 1<<63-1, isWriteLock) 66 } 67 68 // GetRLock tries to get a read lock on lm before the timeout occurs. 69 func (lm *LRWMutex) GetRLock(ctx context.Context, id string, source string, timeout time.Duration) (locked bool) { 70 71 const isWriteLock = false 72 return lm.lockLoop(ctx, id, source, timeout, isWriteLock) 73 } 74 75 func (lm *LRWMutex) lock(id, source string, isWriteLock bool) (locked bool) { 76 lm.mu.Lock() 77 defer lm.mu.Unlock() 78 79 lm.id = id 80 lm.source = source 81 if isWriteLock { 82 if lm.ref == 0 && !lm.isWriteLock { 83 lm.ref = 1 84 lm.isWriteLock = true 85 locked = true 86 } 87 } else { 88 if !lm.isWriteLock { 89 lm.ref++ 90 locked = true 91 } 92 } 93 94 return locked 95 } 96 97 const ( 98 lockRetryInterval = 50 * time.Millisecond 99 ) 100 101 // lockLoop will acquire either a read or a write lock 102 // 103 // The call will block until the lock is granted using a built-in 104 // timing randomized back-off algorithm to try again until successful 105 func (lm *LRWMutex) lockLoop(ctx context.Context, id, source string, timeout time.Duration, isWriteLock bool) (locked bool) { 106 r := rand.New(rand.NewSource(time.Now().UnixNano())) 107 108 retryCtx, cancel := context.WithTimeout(ctx, timeout) 109 defer cancel() 110 111 for { 112 select { 113 case <-retryCtx.Done(): 114 // Caller context canceled or we timedout, 115 // return false anyways for both situations. 116 return false 117 default: 118 if lm.lock(id, source, isWriteLock) { 119 return true 120 } 121 time.Sleep(time.Duration(r.Float64() * float64(lockRetryInterval))) 122 } 123 } 124 } 125 126 // Unlock unlocks the write lock. 127 // 128 // It is a run-time error if lm is not locked on entry to Unlock. 129 func (lm *LRWMutex) Unlock() { 130 131 isWriteLock := true 132 success := lm.unlock(isWriteLock) 133 if !success { 134 panic("Trying to Unlock() while no Lock() is active") 135 } 136 } 137 138 // RUnlock releases a read lock held on lm. 139 // 140 // It is a run-time error if lm is not locked on entry to RUnlock. 141 func (lm *LRWMutex) RUnlock() { 142 143 isWriteLock := false 144 success := lm.unlock(isWriteLock) 145 if !success { 146 panic("Trying to RUnlock() while no RLock() is active") 147 } 148 } 149 150 func (lm *LRWMutex) unlock(isWriteLock bool) (unlocked bool) { 151 lm.mu.Lock() 152 defer lm.mu.Unlock() 153 154 // Try to release lock. 155 if isWriteLock { 156 if lm.isWriteLock && lm.ref == 1 { 157 lm.ref = 0 158 lm.isWriteLock = false 159 unlocked = true 160 } 161 } else { 162 if !lm.isWriteLock { 163 if lm.ref > 0 { 164 lm.ref-- 165 unlocked = true 166 } 167 } 168 } 169 170 return unlocked 171 } 172 173 // ForceUnlock will forcefully clear a write or read lock. 174 func (lm *LRWMutex) ForceUnlock() { 175 lm.mu.Lock() 176 defer lm.mu.Unlock() 177 178 lm.ref = 0 179 lm.isWriteLock = false 180 181 } 182 183 // DRLocker returns a sync.Locker interface that implements 184 // the Lock and Unlock methods by calling drw.RLock and drw.RUnlock. 185 func (lm *LRWMutex) DRLocker() sync.Locker { 186 return (*drlocker)(lm) 187 } 188 189 type drlocker LRWMutex 190 191 func (dr *drlocker) Lock() { (*LRWMutex)(dr).RLock() } 192 func (dr *drlocker) Unlock() { (*LRWMutex)(dr).RUnlock() }