github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/local-locker_test.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 import ( 21 "context" 22 "encoding/hex" 23 "fmt" 24 "math/rand" 25 "testing" 26 "time" 27 28 "github.com/google/uuid" 29 "github.com/minio/minio/internal/dsync" 30 ) 31 32 func TestLocalLockerExpire(t *testing.T) { 33 wResources := make([]string, 1000) 34 rResources := make([]string, 1000) 35 l := newLocker() 36 ctx := context.Background() 37 for i := range wResources { 38 arg := dsync.LockArgs{ 39 UID: mustGetUUID(), 40 Resources: []string{mustGetUUID()}, 41 Source: t.Name(), 42 Owner: "owner", 43 Quorum: 0, 44 } 45 ok, err := l.Lock(ctx, arg) 46 if err != nil { 47 t.Fatal(err) 48 } 49 if !ok { 50 t.Fatal("did not get write lock") 51 } 52 wResources[i] = arg.Resources[0] 53 } 54 for i := range rResources { 55 name := mustGetUUID() 56 arg := dsync.LockArgs{ 57 UID: mustGetUUID(), 58 Resources: []string{name}, 59 Source: t.Name(), 60 Owner: "owner", 61 Quorum: 0, 62 } 63 ok, err := l.RLock(ctx, arg) 64 if err != nil { 65 t.Fatal(err) 66 } 67 if !ok { 68 t.Fatal("did not get write lock") 69 } 70 // RLock twice 71 ok, err = l.RLock(ctx, arg) 72 if err != nil { 73 t.Fatal(err) 74 } 75 if !ok { 76 t.Fatal("did not get write lock") 77 } 78 79 rResources[i] = arg.Resources[0] 80 } 81 if len(l.lockMap) != len(rResources)+len(wResources) { 82 t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), len(rResources), len(wResources)) 83 } 84 if len(l.lockUID) != len(rResources)+len(wResources) { 85 t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources), len(wResources)) 86 } 87 // Expire an hour from now, should keep all 88 l.expireOldLocks(time.Hour) 89 if len(l.lockMap) != len(rResources)+len(wResources) { 90 t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), len(rResources), len(wResources)) 91 } 92 if len(l.lockUID) != len(rResources)+len(wResources) { 93 t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources), len(wResources)) 94 } 95 96 // Expire a minute ago. 97 l.expireOldLocks(-time.Minute) 98 if len(l.lockMap) != 0 { 99 t.Fatalf("after cleanup should be empty, got %d", len(l.lockMap)) 100 } 101 if len(l.lockUID) != 0 { 102 t.Fatalf("lockUID len, got %d, want %d", len(l.lockUID), 0) 103 } 104 } 105 106 func TestLocalLockerUnlock(t *testing.T) { 107 const n = 1000 108 const m = 5 109 wResources := make([][m]string, n) 110 rResources := make([]string, n) 111 wUIDs := make([]string, n) 112 rUIDs := make([]string, 0, n*2) 113 l := newLocker() 114 ctx := context.Background() 115 for i := range wResources { 116 names := [m]string{} 117 for j := range names { 118 names[j] = mustGetUUID() 119 } 120 uid := mustGetUUID() 121 arg := dsync.LockArgs{ 122 UID: uid, 123 Resources: names[:], 124 Source: t.Name(), 125 Owner: "owner", 126 Quorum: 0, 127 } 128 ok, err := l.Lock(ctx, arg) 129 if err != nil { 130 t.Fatal(err) 131 } 132 if !ok { 133 t.Fatal("did not get write lock") 134 } 135 wResources[i] = names 136 wUIDs[i] = uid 137 138 } 139 for i := range rResources { 140 name := mustGetUUID() 141 uid := mustGetUUID() 142 arg := dsync.LockArgs{ 143 UID: uid, 144 Resources: []string{name}, 145 Source: t.Name(), 146 Owner: "owner", 147 Quorum: 0, 148 } 149 ok, err := l.RLock(ctx, arg) 150 if err != nil { 151 t.Fatal(err) 152 } 153 if !ok { 154 t.Fatal("did not get write lock") 155 } 156 rUIDs = append(rUIDs, uid) 157 158 // RLock twice, different uid 159 uid = mustGetUUID() 160 arg.UID = uid 161 ok, err = l.RLock(ctx, arg) 162 if err != nil { 163 t.Fatal(err) 164 } 165 if !ok { 166 t.Fatal("did not get write lock") 167 } 168 rResources[i] = name 169 rUIDs = append(rUIDs, uid) 170 } 171 // Each Lock has m entries 172 if len(l.lockMap) != len(rResources)+len(wResources)*m { 173 t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), len(rResources), len(wResources)*m) 174 } 175 // A UID is added for every resource 176 if len(l.lockUID) != len(rResources)*2+len(wResources)*m { 177 t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources)*2, len(wResources)*m) 178 } 179 // RUnlock once... 180 for i, name := range rResources { 181 arg := dsync.LockArgs{ 182 UID: rUIDs[i*2], 183 Resources: []string{name}, 184 Source: t.Name(), 185 Owner: "owner", 186 Quorum: 0, 187 } 188 ok, err := l.RUnlock(ctx, arg) 189 if err != nil { 190 t.Fatal(err) 191 } 192 if !ok { 193 t.Fatal("did not get write lock") 194 } 195 } 196 197 // Each Lock has m entries 198 if len(l.lockMap) != len(rResources)+len(wResources)*m { 199 t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), len(rResources), len(wResources)*m) 200 } 201 // A UID is added for every resource. 202 // We removed len(rResources) read sources. 203 if len(l.lockUID) != len(rResources)+len(wResources)*m { 204 t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), len(rResources), len(wResources)*m) 205 } 206 // RUnlock again, different uids 207 for i, name := range rResources { 208 arg := dsync.LockArgs{ 209 UID: rUIDs[i*2+1], 210 Resources: []string{name}, 211 Source: "minio", 212 Owner: "owner", 213 Quorum: 0, 214 } 215 ok, err := l.RUnlock(ctx, arg) 216 if err != nil { 217 t.Fatal(err) 218 } 219 if !ok { 220 t.Fatal("did not get write lock") 221 } 222 } 223 224 // Each Lock has m entries 225 if len(l.lockMap) != 0+len(wResources)*m { 226 t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), 0, len(wResources)*m) 227 } 228 // A UID is added for every resource. 229 // We removed Add Rlocked entries 230 if len(l.lockUID) != len(wResources)*m { 231 t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), 0, len(wResources)*m) 232 } 233 234 // Remove write locked 235 for i, names := range wResources { 236 arg := dsync.LockArgs{ 237 UID: wUIDs[i], 238 Resources: names[:], 239 Source: "minio", 240 Owner: "owner", 241 Quorum: 0, 242 } 243 ok, err := l.Unlock(ctx, arg) 244 if err != nil { 245 t.Fatal(err) 246 } 247 if !ok { 248 t.Fatal("did not get write lock") 249 } 250 } 251 252 // All should be gone now... 253 // Each Lock has m entries 254 if len(l.lockMap) != 0 { 255 t.Fatalf("lockmap len, got %d, want %d + %d", len(l.lockMap), 0, 0) 256 } 257 if len(l.lockUID) != 0 { 258 t.Fatalf("lockUID len, got %d, want %d + %d", len(l.lockUID), 0, 0) 259 } 260 } 261 262 func Test_localLocker_expireOldLocksExpire(t *testing.T) { 263 rng := rand.New(rand.NewSource(0)) 264 // Numbers of unique locks 265 for _, locks := range []int{100, 1000, 1e6} { 266 if testing.Short() && locks > 100 { 267 continue 268 } 269 t.Run(fmt.Sprintf("%d-locks", locks), func(t *testing.T) { 270 // Number of readers per lock... 271 for _, readers := range []int{1, 10, 100} { 272 if locks > 1000 && readers > 1 { 273 continue 274 } 275 if testing.Short() && readers > 10 { 276 continue 277 } 278 t.Run(fmt.Sprintf("%d-read", readers), func(t *testing.T) { 279 l := newLocker() 280 for i := 0; i < locks; i++ { 281 var tmp [16]byte 282 rng.Read(tmp[:]) 283 res := []string{hex.EncodeToString(tmp[:])} 284 285 for i := 0; i < readers; i++ { 286 rng.Read(tmp[:]) 287 ok, err := l.RLock(context.Background(), dsync.LockArgs{ 288 UID: uuid.NewString(), 289 Resources: res, 290 Source: hex.EncodeToString(tmp[:8]), 291 Owner: hex.EncodeToString(tmp[8:]), 292 Quorum: 0, 293 }) 294 if !ok || err != nil { 295 t.Fatal("failed:", err, ok) 296 } 297 } 298 } 299 start := time.Now() 300 l.expireOldLocks(time.Hour) 301 t.Logf("Scan Took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap)) 302 if len(l.lockMap) != locks { 303 t.Fatalf("objects deleted, want %d != got %d", locks, len(l.lockMap)) 304 } 305 if len(l.lockUID) != locks*readers { 306 t.Fatalf("objects deleted, want %d != got %d", locks*readers, len(l.lockUID)) 307 } 308 309 // Expire 50% 310 expired := time.Now().Add(-time.Hour * 2) 311 for _, v := range l.lockMap { 312 for i := range v { 313 if rng.Intn(2) == 0 { 314 v[i].TimeLastRefresh = expired 315 } 316 } 317 } 318 start = time.Now() 319 l.expireOldLocks(time.Hour) 320 t.Logf("Expire 50%% took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap)) 321 322 if len(l.lockUID) == locks*readers { 323 t.Fatalf("objects uids all remain, unlikely") 324 } 325 if len(l.lockMap) == 0 { 326 t.Fatalf("objects all deleted, 0 remains") 327 } 328 if len(l.lockUID) == 0 { 329 t.Fatalf("objects uids all deleted, 0 remains") 330 } 331 332 start = time.Now() 333 l.expireOldLocks(-time.Minute) 334 t.Logf("Expire rest took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap)) 335 336 if len(l.lockMap) != 0 { 337 t.Fatalf("objects not deleted, want %d != got %d", 0, len(l.lockMap)) 338 } 339 if len(l.lockUID) != 0 { 340 t.Fatalf("objects not deleted, want %d != got %d", 0, len(l.lockUID)) 341 } 342 }) 343 } 344 }) 345 } 346 } 347 348 func Test_localLocker_RUnlock(t *testing.T) { 349 rng := rand.New(rand.NewSource(0)) 350 // Numbers of unique locks 351 for _, locks := range []int{1, 100, 1000, 1e6} { 352 if testing.Short() && locks > 100 { 353 continue 354 } 355 t.Run(fmt.Sprintf("%d-locks", locks), func(t *testing.T) { 356 // Number of readers per lock... 357 for _, readers := range []int{1, 10, 100} { 358 if locks > 1000 && readers > 1 { 359 continue 360 } 361 if testing.Short() && readers > 10 { 362 continue 363 } 364 t.Run(fmt.Sprintf("%d-read", readers), func(t *testing.T) { 365 l := newLocker() 366 for i := 0; i < locks; i++ { 367 var tmp [16]byte 368 rng.Read(tmp[:]) 369 res := []string{hex.EncodeToString(tmp[:])} 370 371 for i := 0; i < readers; i++ { 372 rng.Read(tmp[:]) 373 ok, err := l.RLock(context.Background(), dsync.LockArgs{ 374 UID: uuid.NewString(), 375 Resources: res, 376 Source: hex.EncodeToString(tmp[:8]), 377 Owner: hex.EncodeToString(tmp[8:]), 378 Quorum: 0, 379 }) 380 if !ok || err != nil { 381 t.Fatal("failed:", err, ok) 382 } 383 } 384 } 385 386 // Expire 50% 387 toUnLock := make([]dsync.LockArgs, 0, locks*readers) 388 for k, v := range l.lockMap { 389 for _, lock := range v { 390 if rng.Intn(2) == 0 { 391 toUnLock = append(toUnLock, dsync.LockArgs{Resources: []string{k}, UID: lock.UID}) 392 } 393 } 394 } 395 start := time.Now() 396 for _, lock := range toUnLock { 397 ok, err := l.ForceUnlock(context.Background(), lock) 398 if err != nil || !ok { 399 t.Fatal(err) 400 } 401 } 402 t.Logf("Expire 50%% took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap)) 403 404 if len(l.lockUID) == locks*readers { 405 t.Fatalf("objects uids all remain, unlikely") 406 } 407 if len(l.lockMap) == 0 && locks > 10 { 408 t.Fatalf("objects all deleted, 0 remains") 409 } 410 if len(l.lockUID) != locks*readers-len(toUnLock) { 411 t.Fatalf("want %d objects uids all deleted, %d remains", len(l.lockUID), locks*readers-len(toUnLock)) 412 } 413 414 toUnLock = toUnLock[:0] 415 for k, v := range l.lockMap { 416 for _, lock := range v { 417 toUnLock = append(toUnLock, dsync.LockArgs{Resources: []string{k}, UID: lock.UID, Owner: lock.Owner}) 418 } 419 } 420 start = time.Now() 421 for _, lock := range toUnLock { 422 ok, err := l.RUnlock(context.TODO(), lock) 423 if err != nil || !ok { 424 t.Fatal(err) 425 } 426 } 427 t.Logf("Expire rest took: %v. Left: %d/%d", time.Since(start).Round(time.Millisecond), len(l.lockUID), len(l.lockMap)) 428 429 if len(l.lockMap) != 0 { 430 t.Fatalf("objects not deleted, want %d != got %d", 0, len(l.lockMap)) 431 } 432 if len(l.lockUID) != 0 { 433 t.Fatalf("objects not deleted, want %d != got %d", 0, len(l.lockUID)) 434 } 435 }) 436 } 437 }) 438 } 439 }