github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/lsync/lrwmutex.go (about) 1 // Copyright (c) 2015-2021 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package lsync 19 20 import ( 21 "context" 22 "math" 23 "math/rand" 24 "sync" 25 "time" 26 ) 27 28 // A LRWMutex is a mutual exclusion lock with timeouts. 29 type LRWMutex struct { 30 id string 31 source string 32 isWriteLock bool 33 ref int 34 mu sync.Mutex // Mutex to prevent multiple simultaneous locks 35 } 36 37 // NewLRWMutex - initializes a new lsync RW mutex. 38 func NewLRWMutex() *LRWMutex { 39 return &LRWMutex{} 40 } 41 42 // Lock holds a write lock on lm. 43 // 44 // If the lock is already in use, the calling go routine 45 // blocks until the mutex is available. 46 func (lm *LRWMutex) Lock() { 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 const isWriteLock = true 54 return lm.lockLoop(ctx, id, source, timeout, isWriteLock) 55 } 56 57 // RLock holds a read lock on lm. 58 // 59 // If one or more read lock are already in use, it will grant another lock. 60 // Otherwise the calling go routine blocks until the mutex is available. 61 func (lm *LRWMutex) RLock() { 62 const isWriteLock = false 63 lm.lockLoop(context.Background(), lm.id, lm.source, 1<<63-1, isWriteLock) 64 } 65 66 // GetRLock tries to get a read lock on lm before the timeout occurs. 67 func (lm *LRWMutex) GetRLock(ctx context.Context, id string, source string, timeout time.Duration) (locked bool) { 68 const isWriteLock = false 69 return lm.lockLoop(ctx, id, source, timeout, isWriteLock) 70 } 71 72 func (lm *LRWMutex) lock(id, source string, isWriteLock bool) (locked bool) { 73 lm.mu.Lock() 74 defer lm.mu.Unlock() 75 76 lm.id = id 77 lm.source = source 78 if isWriteLock { 79 if lm.ref == 0 && !lm.isWriteLock { 80 lm.ref = 1 81 lm.isWriteLock = true 82 locked = true 83 } 84 } else { 85 if !lm.isWriteLock { 86 lm.ref++ 87 locked = true 88 } 89 } 90 91 return locked 92 } 93 94 const ( 95 lockRetryInterval = 50 * time.Millisecond 96 ) 97 98 // lockLoop will acquire either a read or a write lock 99 // 100 // The call will block until the lock is granted using a built-in 101 // timing randomized back-off algorithm to try again until successful 102 func (lm *LRWMutex) lockLoop(ctx context.Context, id, source string, timeout time.Duration, isWriteLock bool) (locked bool) { 103 r := rand.New(rand.NewSource(time.Now().UnixNano())) 104 105 retryCtx, cancel := context.WithTimeout(ctx, timeout) 106 defer cancel() 107 108 for { 109 select { 110 case <-retryCtx.Done(): 111 // Caller context canceled or we timedout, 112 // return false anyways for both situations. 113 return false 114 default: 115 if lm.lock(id, source, isWriteLock) { 116 return true 117 } 118 time.Sleep(time.Duration(r.Float64() * float64(lockRetryInterval))) 119 } 120 } 121 } 122 123 // Unlock unlocks the write lock. 124 // 125 // It is a run-time error if lm is not locked on entry to Unlock. 126 func (lm *LRWMutex) Unlock() { 127 isWriteLock := true 128 success := lm.unlock(isWriteLock) 129 if !success { 130 panic("Trying to Unlock() while no Lock() is active") 131 } 132 } 133 134 // RUnlock releases a read lock held on lm. 135 // 136 // It is a run-time error if lm is not locked on entry to RUnlock. 137 func (lm *LRWMutex) RUnlock() { 138 isWriteLock := false 139 success := lm.unlock(isWriteLock) 140 if !success { 141 panic("Trying to RUnlock() while no RLock() is active") 142 } 143 } 144 145 func (lm *LRWMutex) unlock(isWriteLock bool) (unlocked bool) { 146 lm.mu.Lock() 147 defer lm.mu.Unlock() 148 149 // Try to release lock. 150 if isWriteLock { 151 if lm.isWriteLock && lm.ref == 1 { 152 lm.ref = 0 153 lm.isWriteLock = false 154 unlocked = true 155 } 156 } else { 157 if !lm.isWriteLock { 158 if lm.ref > 0 { 159 lm.ref-- 160 unlocked = true 161 } 162 } 163 } 164 165 return unlocked 166 } 167 168 // ForceUnlock will forcefully clear a write or read lock. 169 func (lm *LRWMutex) ForceUnlock() { 170 lm.mu.Lock() 171 defer lm.mu.Unlock() 172 173 lm.ref = 0 174 lm.isWriteLock = false 175 } 176 177 // DRLocker returns a sync.Locker interface that implements 178 // the Lock and Unlock methods by calling drw.RLock and drw.RUnlock. 179 func (lm *LRWMutex) DRLocker() sync.Locker { 180 return (*drlocker)(lm) 181 } 182 183 type drlocker LRWMutex 184 185 func (dr *drlocker) Lock() { (*LRWMutex)(dr).RLock() } 186 func (dr *drlocker) Unlock() { (*LRWMutex)(dr).RUnlock() }