storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/namespace-lock.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2016-2021 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 cmd 18 19 import ( 20 "context" 21 "errors" 22 pathutil "path" 23 "runtime" 24 "sort" 25 "strings" 26 "sync" 27 28 "fmt" 29 "time" 30 31 "storj.io/minio/cmd/logger" 32 "storj.io/minio/pkg/dsync" 33 "storj.io/minio/pkg/lsync" 34 ) 35 36 // local lock servers 37 var globalLockServer *localLocker 38 39 // RWLocker - locker interface to introduce GetRLock, RUnlock. 40 type RWLocker interface { 41 GetLock(ctx context.Context, timeout *dynamicTimeout) (newCtx context.Context, timedOutErr error) 42 Unlock() 43 GetRLock(ctx context.Context, timeout *dynamicTimeout) (newCtx context.Context, timedOutErr error) 44 RUnlock() 45 } 46 47 // newNSLock - return a new name space lock map. 48 func newNSLock(isDistErasure bool) *nsLockMap { 49 nsMutex := nsLockMap{ 50 isDistErasure: isDistErasure, 51 } 52 if isDistErasure { 53 return &nsMutex 54 } 55 nsMutex.lockMap = make(map[string]*nsLock) 56 return &nsMutex 57 } 58 59 // nsLock - provides primitives for locking critical namespace regions. 60 type nsLock struct { 61 ref int32 62 *lsync.LRWMutex 63 } 64 65 // nsLockMap - namespace lock map, provides primitives to Lock, 66 // Unlock, RLock and RUnlock. 67 type nsLockMap struct { 68 // Indicates if namespace is part of a distributed setup. 69 isDistErasure bool 70 lockMap map[string]*nsLock 71 lockMapMutex sync.Mutex 72 } 73 74 // Lock the namespace resource. 75 func (n *nsLockMap) lock(ctx context.Context, volume string, path string, lockSource, opsID string, readLock bool, timeout time.Duration) (locked bool) { 76 resource := pathJoin(volume, path) 77 78 n.lockMapMutex.Lock() 79 nsLk, found := n.lockMap[resource] 80 if !found { 81 nsLk = &nsLock{ 82 LRWMutex: lsync.NewLRWMutex(), 83 } 84 // Add a count to indicate that a parallel unlock doesn't clear this entry. 85 } 86 nsLk.ref++ 87 n.lockMap[resource] = nsLk 88 n.lockMapMutex.Unlock() 89 90 // Locking here will block (until timeout). 91 if readLock { 92 locked = nsLk.GetRLock(ctx, opsID, lockSource, timeout) 93 } else { 94 locked = nsLk.GetLock(ctx, opsID, lockSource, timeout) 95 } 96 97 if !locked { // We failed to get the lock 98 // Decrement ref count since we failed to get the lock 99 n.lockMapMutex.Lock() 100 n.lockMap[resource].ref-- 101 if n.lockMap[resource].ref < 0 { 102 logger.CriticalIf(GlobalContext, errors.New("resource reference count was lower than 0")) 103 } 104 if n.lockMap[resource].ref == 0 { 105 // Remove from the map if there are no more references. 106 delete(n.lockMap, resource) 107 } 108 n.lockMapMutex.Unlock() 109 } 110 111 return 112 } 113 114 // Unlock the namespace resource. 115 func (n *nsLockMap) unlock(volume string, path string, readLock bool) { 116 resource := pathJoin(volume, path) 117 118 n.lockMapMutex.Lock() 119 defer n.lockMapMutex.Unlock() 120 if _, found := n.lockMap[resource]; !found { 121 return 122 } 123 if readLock { 124 n.lockMap[resource].RUnlock() 125 } else { 126 n.lockMap[resource].Unlock() 127 } 128 n.lockMap[resource].ref-- 129 if n.lockMap[resource].ref < 0 { 130 logger.CriticalIf(GlobalContext, errors.New("resource reference count was lower than 0")) 131 } 132 if n.lockMap[resource].ref == 0 { 133 // Remove from the map if there are no more references. 134 delete(n.lockMap, resource) 135 } 136 } 137 138 // dsync's distributed lock instance. 139 type distLockInstance struct { 140 rwMutex *dsync.DRWMutex 141 opsID string 142 } 143 144 // Lock - block until write lock is taken or timeout has occurred. 145 func (di *distLockInstance) GetLock(ctx context.Context, timeout *dynamicTimeout) (context.Context, error) { 146 lockSource := getSource(2) 147 start := UTCNow() 148 149 newCtx, cancel := context.WithCancel(ctx) 150 if !di.rwMutex.GetLock(newCtx, cancel, di.opsID, lockSource, dsync.Options{ 151 Timeout: timeout.Timeout(), 152 }) { 153 timeout.LogFailure() 154 return ctx, OperationTimedOut{} 155 } 156 timeout.LogSuccess(UTCNow().Sub(start)) 157 return newCtx, nil 158 } 159 160 // Unlock - block until write lock is released. 161 func (di *distLockInstance) Unlock() { 162 di.rwMutex.Unlock() 163 } 164 165 // RLock - block until read lock is taken or timeout has occurred. 166 func (di *distLockInstance) GetRLock(ctx context.Context, timeout *dynamicTimeout) (context.Context, error) { 167 lockSource := getSource(2) 168 start := UTCNow() 169 170 newCtx, cancel := context.WithCancel(ctx) 171 if !di.rwMutex.GetRLock(ctx, cancel, di.opsID, lockSource, dsync.Options{ 172 Timeout: timeout.Timeout(), 173 }) { 174 timeout.LogFailure() 175 return ctx, OperationTimedOut{} 176 } 177 timeout.LogSuccess(UTCNow().Sub(start)) 178 return newCtx, nil 179 } 180 181 // RUnlock - block until read lock is released. 182 func (di *distLockInstance) RUnlock() { 183 di.rwMutex.RUnlock() 184 } 185 186 // localLockInstance - frontend/top-level interface for namespace locks. 187 type localLockInstance struct { 188 ns *nsLockMap 189 volume string 190 paths []string 191 opsID string 192 } 193 194 // NewNSLock - returns a lock instance for a given volume and 195 // path. The returned lockInstance object encapsulates the nsLockMap, 196 // volume, path and operation ID. 197 func (n *nsLockMap) NewNSLock(lockers func() ([]dsync.NetLocker, string), volume string, paths ...string) RWLocker { 198 opsID := mustGetUUID() 199 if n.isDistErasure { 200 drwmutex := dsync.NewDRWMutex(&dsync.Dsync{ 201 GetLockers: lockers, 202 }, pathsJoinPrefix(volume, paths...)...) 203 return &distLockInstance{drwmutex, opsID} 204 } 205 sort.Strings(paths) 206 return &localLockInstance{n, volume, paths, opsID} 207 } 208 209 // Lock - block until write lock is taken or timeout has occurred. 210 func (li *localLockInstance) GetLock(ctx context.Context, timeout *dynamicTimeout) (_ context.Context, timedOutErr error) { 211 lockSource := getSource(2) 212 start := UTCNow() 213 const readLock = false 214 success := make([]int, len(li.paths)) 215 for i, path := range li.paths { 216 if !li.ns.lock(ctx, li.volume, path, lockSource, li.opsID, readLock, timeout.Timeout()) { 217 timeout.LogFailure() 218 for si, sint := range success { 219 if sint == 1 { 220 li.ns.unlock(li.volume, li.paths[si], readLock) 221 } 222 } 223 return nil, OperationTimedOut{} 224 } 225 success[i] = 1 226 } 227 timeout.LogSuccess(UTCNow().Sub(start)) 228 return ctx, nil 229 } 230 231 // Unlock - block until write lock is released. 232 func (li *localLockInstance) Unlock() { 233 const readLock = false 234 for _, path := range li.paths { 235 li.ns.unlock(li.volume, path, readLock) 236 } 237 } 238 239 // RLock - block until read lock is taken or timeout has occurred. 240 func (li *localLockInstance) GetRLock(ctx context.Context, timeout *dynamicTimeout) (_ context.Context, timedOutErr error) { 241 lockSource := getSource(2) 242 start := UTCNow() 243 const readLock = true 244 success := make([]int, len(li.paths)) 245 for i, path := range li.paths { 246 if !li.ns.lock(ctx, li.volume, path, lockSource, li.opsID, readLock, timeout.Timeout()) { 247 timeout.LogFailure() 248 for si, sint := range success { 249 if sint == 1 { 250 li.ns.unlock(li.volume, li.paths[si], readLock) 251 } 252 } 253 return nil, OperationTimedOut{} 254 } 255 success[i] = 1 256 } 257 timeout.LogSuccess(UTCNow().Sub(start)) 258 return ctx, nil 259 } 260 261 // RUnlock - block until read lock is released. 262 func (li *localLockInstance) RUnlock() { 263 const readLock = true 264 for _, path := range li.paths { 265 li.ns.unlock(li.volume, path, readLock) 266 } 267 } 268 269 func getSource(n int) string { 270 var funcName string 271 pc, filename, lineNum, ok := runtime.Caller(n) 272 if ok { 273 filename = pathutil.Base(filename) 274 funcName = strings.TrimPrefix(runtime.FuncForPC(pc).Name(), 275 "github.com/minio/minio/cmd.") 276 } else { 277 filename = "<unknown>" 278 lineNum = 0 279 } 280 281 return fmt.Sprintf("[%s:%d:%s()]", filename, lineNum, funcName) 282 }