github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/local-locker.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 cmd 19 20 //go:generate msgp -file=$GOFILE -unexported 21 22 import ( 23 "context" 24 "fmt" 25 "strconv" 26 "sync" 27 "time" 28 29 "github.com/minio/minio/internal/dsync" 30 ) 31 32 // lockRequesterInfo stores various info from the client for each lock that is requested. 33 type lockRequesterInfo struct { 34 Name string // name of the resource lock was requested for 35 Writer bool // Bool whether write or read lock. 36 UID string // UID to uniquely identify request of client. 37 Timestamp time.Time // Timestamp set at the time of initialization. 38 TimeLastRefresh time.Time // Timestamp for last lock refresh. 39 Source string // Contains line, function and filename requesting the lock. 40 Group bool // indicates if it was a group lock. 41 Owner string // Owner represents the UUID of the owner who originally requested the lock. 42 Quorum int // Quorum represents the quorum required for this lock to be active. 43 idx int `msg:"-"` // index of the lock in the lockMap. 44 } 45 46 // isWriteLock returns whether the lock is a write or read lock. 47 func isWriteLock(lri []lockRequesterInfo) bool { 48 return len(lri) == 1 && lri[0].Writer 49 } 50 51 // localLocker implements Dsync.NetLocker 52 // 53 //msgp:ignore localLocker 54 type localLocker struct { 55 mutex sync.Mutex 56 lockMap map[string][]lockRequesterInfo 57 lockUID map[string]string // UUID -> resource map. 58 } 59 60 func (l *localLocker) String() string { 61 return globalEndpoints.Localhost() 62 } 63 64 func (l *localLocker) canTakeLock(resources ...string) bool { 65 for _, resource := range resources { 66 _, lockTaken := l.lockMap[resource] 67 if lockTaken { 68 return false 69 } 70 } 71 return true 72 } 73 74 func (l *localLocker) Lock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) { 75 if len(args.Resources) > maxDeleteList { 76 return false, fmt.Errorf("internal error: localLocker.Lock called with more than %d resources", maxDeleteList) 77 } 78 79 l.mutex.Lock() 80 defer l.mutex.Unlock() 81 82 if !l.canTakeLock(args.Resources...) { 83 // Not all locks can be taken on resources, 84 // reject it completely. 85 return false, nil 86 } 87 88 // No locks held on the all resources, so claim write 89 // lock on all resources at once. 90 for i, resource := range args.Resources { 91 l.lockMap[resource] = []lockRequesterInfo{ 92 { 93 Name: resource, 94 Writer: true, 95 Source: args.Source, 96 Owner: args.Owner, 97 UID: args.UID, 98 Timestamp: UTCNow(), 99 TimeLastRefresh: UTCNow(), 100 Group: len(args.Resources) > 1, 101 Quorum: args.Quorum, 102 idx: i, 103 }, 104 } 105 l.lockUID[formatUUID(args.UID, i)] = resource 106 } 107 return true, nil 108 } 109 110 func formatUUID(s string, idx int) string { 111 return s + strconv.Itoa(idx) 112 } 113 114 func (l *localLocker) Unlock(_ context.Context, args dsync.LockArgs) (reply bool, err error) { 115 if len(args.Resources) > maxDeleteList { 116 return false, fmt.Errorf("internal error: localLocker.Unlock called with more than %d resources", maxDeleteList) 117 } 118 119 l.mutex.Lock() 120 defer l.mutex.Unlock() 121 err = nil 122 123 for _, resource := range args.Resources { 124 lri, ok := l.lockMap[resource] 125 if ok && !isWriteLock(lri) { 126 // Unless it is a write lock reject it. 127 err = fmt.Errorf("unlock attempted on a read locked entity: %s", resource) 128 continue 129 } 130 if ok { 131 reply = l.removeEntry(resource, args, &lri) || reply 132 } 133 } 134 return 135 } 136 137 // removeEntry based on the uid of the lock message, removes a single entry from the 138 // lockRequesterInfo array or the whole array from the map (in case of a write lock 139 // or last read lock) 140 // UID and optionally owner must match for entries to be deleted. 141 func (l *localLocker) removeEntry(name string, args dsync.LockArgs, lri *[]lockRequesterInfo) bool { 142 // Find correct entry to remove based on uid. 143 for index, entry := range *lri { 144 if entry.UID == args.UID && (args.Owner == "" || entry.Owner == args.Owner) { 145 if len(*lri) == 1 { 146 // Remove the write lock. 147 delete(l.lockMap, name) 148 } else { 149 // Remove the appropriate read lock. 150 *lri = append((*lri)[:index], (*lri)[index+1:]...) 151 l.lockMap[name] = *lri 152 } 153 delete(l.lockUID, formatUUID(args.UID, entry.idx)) 154 return true 155 } 156 } 157 158 // None found return false, perhaps entry removed in previous run. 159 return false 160 } 161 162 func (l *localLocker) RLock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) { 163 if len(args.Resources) != 1 { 164 return false, fmt.Errorf("internal error: localLocker.RLock called with more than one resource") 165 } 166 167 l.mutex.Lock() 168 defer l.mutex.Unlock() 169 resource := args.Resources[0] 170 lrInfo := lockRequesterInfo{ 171 Name: resource, 172 Writer: false, 173 Source: args.Source, 174 Owner: args.Owner, 175 UID: args.UID, 176 Timestamp: UTCNow(), 177 TimeLastRefresh: UTCNow(), 178 Quorum: args.Quorum, 179 } 180 if lri, ok := l.lockMap[resource]; ok { 181 if reply = !isWriteLock(lri); reply { 182 // Unless there is a write lock 183 l.lockMap[resource] = append(l.lockMap[resource], lrInfo) 184 l.lockUID[formatUUID(args.UID, 0)] = resource 185 } 186 } else { 187 // No locks held on the given name, so claim (first) read lock 188 l.lockMap[resource] = []lockRequesterInfo{lrInfo} 189 l.lockUID[formatUUID(args.UID, 0)] = resource 190 reply = true 191 } 192 return reply, nil 193 } 194 195 func (l *localLocker) RUnlock(_ context.Context, args dsync.LockArgs) (reply bool, err error) { 196 if len(args.Resources) > 1 { 197 return false, fmt.Errorf("internal error: localLocker.RUnlock called with more than one resource") 198 } 199 200 l.mutex.Lock() 201 defer l.mutex.Unlock() 202 var lri []lockRequesterInfo 203 204 resource := args.Resources[0] 205 if lri, reply = l.lockMap[resource]; !reply { 206 // No lock is held on the given name 207 return true, nil 208 } 209 if isWriteLock(lri) { 210 // A write-lock is held, cannot release a read lock 211 return false, fmt.Errorf("RUnlock attempted on a write locked entity: %s", resource) 212 } 213 l.removeEntry(resource, args, &lri) 214 return reply, nil 215 } 216 217 type lockStats struct { 218 Total int 219 Writes int 220 Reads int 221 } 222 223 func (l *localLocker) stats() lockStats { 224 l.mutex.Lock() 225 defer l.mutex.Unlock() 226 227 st := lockStats{Total: len(l.lockMap)} 228 for _, v := range l.lockMap { 229 if len(v) == 0 { 230 continue 231 } 232 entry := v[0] 233 if entry.Writer { 234 st.Writes++ 235 } else { 236 st.Reads += len(v) 237 } 238 } 239 return st 240 } 241 242 type localLockMap map[string][]lockRequesterInfo 243 244 func (l *localLocker) DupLockMap() localLockMap { 245 l.mutex.Lock() 246 defer l.mutex.Unlock() 247 248 lockCopy := make(map[string][]lockRequesterInfo, len(l.lockMap)) 249 for k, v := range l.lockMap { 250 if len(v) == 0 { 251 delete(l.lockMap, k) 252 continue 253 } 254 lockCopy[k] = append(make([]lockRequesterInfo, 0, len(v)), v...) 255 } 256 return lockCopy 257 } 258 259 func (l *localLocker) Close() error { 260 return nil 261 } 262 263 // IsOnline - local locker is always online. 264 func (l *localLocker) IsOnline() bool { 265 return true 266 } 267 268 // IsLocal - local locker returns true. 269 func (l *localLocker) IsLocal() bool { 270 return true 271 } 272 273 func (l *localLocker) ForceUnlock(ctx context.Context, args dsync.LockArgs) (reply bool, err error) { 274 select { 275 case <-ctx.Done(): 276 return false, ctx.Err() 277 default: 278 l.mutex.Lock() 279 defer l.mutex.Unlock() 280 if len(args.UID) == 0 { 281 for _, resource := range args.Resources { 282 lris, ok := l.lockMap[resource] 283 if !ok { 284 continue 285 } 286 // Collect uids, so we don't mutate while we delete 287 uids := make([]string, 0, len(lris)) 288 for _, lri := range lris { 289 uids = append(uids, lri.UID) 290 } 291 292 // Delete collected uids: 293 for _, uid := range uids { 294 lris, ok := l.lockMap[resource] 295 if !ok { 296 // Just to be safe, delete uuids. 297 for idx := 0; idx < maxDeleteList; idx++ { 298 mapID := formatUUID(uid, idx) 299 if _, ok := l.lockUID[mapID]; !ok { 300 break 301 } 302 delete(l.lockUID, mapID) 303 } 304 continue 305 } 306 l.removeEntry(resource, dsync.LockArgs{UID: uid}, &lris) 307 } 308 } 309 return true, nil 310 } 311 312 idx := 0 313 for { 314 mapID := formatUUID(args.UID, idx) 315 resource, ok := l.lockUID[mapID] 316 if !ok { 317 return idx > 0, nil 318 } 319 lris, ok := l.lockMap[resource] 320 if !ok { 321 // Unexpected inconsistency, delete. 322 delete(l.lockUID, mapID) 323 idx++ 324 continue 325 } 326 reply = true 327 l.removeEntry(resource, dsync.LockArgs{UID: args.UID}, &lris) 328 idx++ 329 } 330 } 331 } 332 333 func (l *localLocker) Refresh(ctx context.Context, args dsync.LockArgs) (refreshed bool, err error) { 334 select { 335 case <-ctx.Done(): 336 return false, ctx.Err() 337 default: 338 l.mutex.Lock() 339 defer l.mutex.Unlock() 340 341 // Check whether uid is still active. 342 resource, ok := l.lockUID[formatUUID(args.UID, 0)] 343 if !ok { 344 return false, nil 345 } 346 idx := 0 347 for { 348 lris, ok := l.lockMap[resource] 349 if !ok { 350 // Inconsistent. Delete UID. 351 delete(l.lockUID, formatUUID(args.UID, idx)) 352 return idx > 0, nil 353 } 354 for i := range lris { 355 if lris[i].UID == args.UID { 356 lris[i].TimeLastRefresh = UTCNow() 357 } 358 } 359 idx++ 360 resource, ok = l.lockUID[formatUUID(args.UID, idx)] 361 if !ok { 362 // No more resources for UID, but we did update at least one. 363 return true, nil 364 } 365 } 366 } 367 } 368 369 // Similar to removeEntry but only removes an entry only if the lock entry exists in map. 370 // Caller must hold 'l.mutex' lock. 371 func (l *localLocker) expireOldLocks(interval time.Duration) { 372 l.mutex.Lock() 373 defer l.mutex.Unlock() 374 375 for k, lris := range l.lockMap { 376 modified := false 377 for i := 0; i < len(lris); { 378 lri := &lris[i] 379 if time.Since(lri.TimeLastRefresh) > interval { 380 delete(l.lockUID, formatUUID(lri.UID, lri.idx)) 381 if len(lris) == 1 { 382 // Remove the write lock. 383 delete(l.lockMap, lri.Name) 384 modified = false 385 break 386 } 387 modified = true 388 // Remove the appropriate lock. 389 lris = append(lris[:i], lris[i+1:]...) 390 // Check same i 391 } else { 392 // Move to next 393 i++ 394 } 395 } 396 if modified { 397 l.lockMap[k] = lris 398 } 399 } 400 } 401 402 func newLocker() *localLocker { 403 return &localLocker{ 404 lockMap: make(map[string][]lockRequesterInfo, 1000), 405 lockUID: make(map[string]string, 1000), 406 } 407 }