github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/dsync/drwmutex_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 dsync 19 20 import ( 21 "context" 22 "fmt" 23 "runtime" 24 "sync/atomic" 25 "testing" 26 "time" 27 ) 28 29 const ( 30 id = "1234-5678" 31 source = "main.go" 32 ) 33 34 func testSimpleWriteLock(t *testing.T, duration time.Duration) (locked bool) { 35 drwm1 := NewDRWMutex(ds, "simplelock") 36 ctx1, cancel1 := context.WithCancel(context.Background()) 37 if !drwm1.GetRLock(ctx1, cancel1, id, source, Options{Timeout: time.Second}) { 38 panic("Failed to acquire read lock") 39 } 40 // fmt.Println("1st read lock acquired, waiting...") 41 42 drwm2 := NewDRWMutex(ds, "simplelock") 43 ctx2, cancel2 := context.WithCancel(context.Background()) 44 if !drwm2.GetRLock(ctx2, cancel2, id, source, Options{Timeout: time.Second}) { 45 panic("Failed to acquire read lock") 46 } 47 // fmt.Println("2nd read lock acquired, waiting...") 48 49 go func() { 50 time.Sleep(2 * testDrwMutexAcquireTimeout) 51 drwm1.RUnlock(context.Background()) 52 // fmt.Println("1st read lock released, waiting...") 53 }() 54 55 go func() { 56 time.Sleep(3 * testDrwMutexAcquireTimeout) 57 drwm2.RUnlock(context.Background()) 58 // fmt.Println("2nd read lock released, waiting...") 59 }() 60 61 drwm3 := NewDRWMutex(ds, "simplelock") 62 // fmt.Println("Trying to acquire write lock, waiting...") 63 ctx3, cancel3 := context.WithCancel(context.Background()) 64 locked = drwm3.GetLock(ctx3, cancel3, id, source, Options{Timeout: duration}) 65 if locked { 66 // fmt.Println("Write lock acquired, waiting...") 67 time.Sleep(testDrwMutexAcquireTimeout) 68 69 drwm3.Unlock(context.Background()) 70 } 71 // fmt.Println("Write lock failed due to timeout") 72 return 73 } 74 75 func TestSimpleWriteLockAcquired(t *testing.T) { 76 locked := testSimpleWriteLock(t, 10*testDrwMutexAcquireTimeout) 77 78 expected := true 79 if locked != expected { 80 t.Errorf("TestSimpleWriteLockAcquired(): \nexpected %#v\ngot %#v", expected, locked) 81 } 82 } 83 84 func TestSimpleWriteLockTimedOut(t *testing.T) { 85 locked := testSimpleWriteLock(t, testDrwMutexAcquireTimeout) 86 87 expected := false 88 if locked != expected { 89 t.Errorf("TestSimpleWriteLockTimedOut(): \nexpected %#v\ngot %#v", expected, locked) 90 } 91 } 92 93 func testDualWriteLock(t *testing.T, duration time.Duration) (locked bool) { 94 drwm1 := NewDRWMutex(ds, "duallock") 95 96 // fmt.Println("Getting initial write lock") 97 ctx1, cancel1 := context.WithCancel(context.Background()) 98 if !drwm1.GetLock(ctx1, cancel1, id, source, Options{Timeout: time.Second}) { 99 panic("Failed to acquire initial write lock") 100 } 101 102 go func() { 103 time.Sleep(3 * testDrwMutexAcquireTimeout) 104 drwm1.Unlock(context.Background()) 105 // fmt.Println("Initial write lock released, waiting...") 106 }() 107 108 // fmt.Println("Trying to acquire 2nd write lock, waiting...") 109 drwm2 := NewDRWMutex(ds, "duallock") 110 ctx2, cancel2 := context.WithCancel(context.Background()) 111 locked = drwm2.GetLock(ctx2, cancel2, id, source, Options{Timeout: duration}) 112 if locked { 113 // fmt.Println("2nd write lock acquired, waiting...") 114 time.Sleep(testDrwMutexAcquireTimeout) 115 116 drwm2.Unlock(context.Background()) 117 } 118 // fmt.Println("2nd write lock failed due to timeout") 119 return 120 } 121 122 func TestDualWriteLockAcquired(t *testing.T) { 123 locked := testDualWriteLock(t, 10*testDrwMutexAcquireTimeout) 124 125 expected := true 126 if locked != expected { 127 t.Errorf("TestDualWriteLockAcquired(): \nexpected %#v\ngot %#v", expected, locked) 128 } 129 } 130 131 func TestDualWriteLockTimedOut(t *testing.T) { 132 locked := testDualWriteLock(t, testDrwMutexAcquireTimeout) 133 134 expected := false 135 if locked != expected { 136 t.Errorf("TestDualWriteLockTimedOut(): \nexpected %#v\ngot %#v", expected, locked) 137 } 138 } 139 140 // Test cases below are copied 1 to 1 from sync/rwmutex_test.go (adapted to use DRWMutex) 141 142 // Borrowed from rwmutex_test.go 143 func parallelReader(ctx context.Context, m *DRWMutex, clocked, cunlock, cdone chan bool) { 144 if m.GetRLock(ctx, nil, id, source, Options{Timeout: time.Second}) { 145 clocked <- true 146 <-cunlock 147 m.RUnlock(context.Background()) 148 cdone <- true 149 } 150 } 151 152 // Borrowed from rwmutex_test.go 153 func doTestParallelReaders(numReaders, gomaxprocs int) { 154 runtime.GOMAXPROCS(gomaxprocs) 155 m := NewDRWMutex(ds, "test-parallel") 156 157 clocked := make(chan bool) 158 cunlock := make(chan bool) 159 cdone := make(chan bool) 160 for i := 0; i < numReaders; i++ { 161 go parallelReader(context.Background(), m, clocked, cunlock, cdone) 162 } 163 // Wait for all parallel RLock()s to succeed. 164 for i := 0; i < numReaders; i++ { 165 <-clocked 166 } 167 for i := 0; i < numReaders; i++ { 168 cunlock <- true 169 } 170 // Wait for the goroutines to finish. 171 for i := 0; i < numReaders; i++ { 172 <-cdone 173 } 174 } 175 176 // Borrowed from rwmutex_test.go 177 func TestParallelReaders(t *testing.T) { 178 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) 179 doTestParallelReaders(1, 4) 180 doTestParallelReaders(3, 4) 181 doTestParallelReaders(4, 2) 182 } 183 184 // Borrowed from rwmutex_test.go 185 func reader(resource string, numIterations int, activity *int32, cdone chan bool) { 186 rwm := NewDRWMutex(ds, resource) 187 for i := 0; i < numIterations; i++ { 188 if rwm.GetRLock(context.Background(), nil, id, source, Options{Timeout: time.Second}) { 189 n := atomic.AddInt32(activity, 1) 190 if n < 1 || n >= 10000 { 191 panic(fmt.Sprintf("wlock(%d)\n", n)) 192 } 193 for i := 0; i < 100; i++ { 194 } 195 atomic.AddInt32(activity, -1) 196 rwm.RUnlock(context.Background()) 197 } 198 } 199 cdone <- true 200 } 201 202 // Borrowed from rwmutex_test.go 203 func writer(resource string, numIterations int, activity *int32, cdone chan bool) { 204 rwm := NewDRWMutex(ds, resource) 205 for i := 0; i < numIterations; i++ { 206 if rwm.GetLock(context.Background(), nil, id, source, Options{Timeout: time.Second}) { 207 n := atomic.AddInt32(activity, 10000) 208 if n != 10000 { 209 panic(fmt.Sprintf("wlock(%d)\n", n)) 210 } 211 for i := 0; i < 100; i++ { 212 } 213 atomic.AddInt32(activity, -10000) 214 rwm.Unlock(context.Background()) 215 } 216 } 217 cdone <- true 218 } 219 220 // Borrowed from rwmutex_test.go 221 func hammerRWMutex(t *testing.T, gomaxprocs, numReaders, numIterations int) { 222 t.Run(fmt.Sprintf("%d-%d-%d", gomaxprocs, numReaders, numIterations), func(t *testing.T) { 223 resource := "test" 224 runtime.GOMAXPROCS(gomaxprocs) 225 // Number of active readers + 10000 * number of active writers. 226 var activity int32 227 cdone := make(chan bool) 228 go writer(resource, numIterations, &activity, cdone) 229 var i int 230 for i = 0; i < numReaders/2; i++ { 231 go reader(resource, numIterations, &activity, cdone) 232 } 233 go writer(resource, numIterations, &activity, cdone) 234 for ; i < numReaders; i++ { 235 go reader(resource, numIterations, &activity, cdone) 236 } 237 // Wait for the 2 writers and all readers to finish. 238 for i := 0; i < 2+numReaders; i++ { 239 <-cdone 240 } 241 }) 242 } 243 244 // Borrowed from rwmutex_test.go 245 func TestRWMutex(t *testing.T) { 246 defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1)) 247 n := 100 248 if testing.Short() { 249 n = 5 250 } 251 hammerRWMutex(t, 1, 1, n) 252 hammerRWMutex(t, 1, 3, n) 253 hammerRWMutex(t, 1, 10, n) 254 hammerRWMutex(t, 4, 1, n) 255 hammerRWMutex(t, 4, 3, n) 256 hammerRWMutex(t, 4, 10, n) 257 hammerRWMutex(t, 10, 1, n) 258 hammerRWMutex(t, 10, 3, n) 259 hammerRWMutex(t, 10, 10, n) 260 hammerRWMutex(t, 10, 5, n) 261 } 262 263 // Borrowed from rwmutex_test.go 264 func TestUnlockPanic(t *testing.T) { 265 defer func() { 266 if recover() == nil { 267 t.Fatalf("unlock of unlocked RWMutex did not panic") 268 } 269 }() 270 mu := NewDRWMutex(ds, "test") 271 mu.Unlock(context.Background()) 272 } 273 274 // Borrowed from rwmutex_test.go 275 func TestUnlockPanic2(t *testing.T) { 276 mu := NewDRWMutex(ds, "test-unlock-panic-2") 277 defer func() { 278 if recover() == nil { 279 t.Fatalf("unlock of unlocked RWMutex did not panic") 280 } 281 mu.RUnlock(context.Background()) // Unlock, so -test.count > 1 works 282 }() 283 mu.RLock(id, source) 284 mu.Unlock(context.Background()) 285 } 286 287 // Borrowed from rwmutex_test.go 288 func TestRUnlockPanic(t *testing.T) { 289 defer func() { 290 if recover() == nil { 291 t.Fatalf("read unlock of unlocked RWMutex did not panic") 292 } 293 }() 294 mu := NewDRWMutex(ds, "test") 295 mu.RUnlock(context.Background()) 296 } 297 298 // Borrowed from rwmutex_test.go 299 func TestRUnlockPanic2(t *testing.T) { 300 mu := NewDRWMutex(ds, "test-runlock-panic-2") 301 defer func() { 302 if recover() == nil { 303 t.Fatalf("read unlock of unlocked RWMutex did not panic") 304 } 305 mu.Unlock(context.Background()) // Unlock, so -test.count > 1 works 306 }() 307 mu.Lock(id, source) 308 mu.RUnlock(context.Background()) 309 } 310 311 // Borrowed from rwmutex_test.go 312 func benchmarkRWMutex(b *testing.B, localWork, writeRatio int) { 313 b.ResetTimer() 314 b.ReportAllocs() 315 316 b.RunParallel(func(pb *testing.PB) { 317 foo := 0 318 for pb.Next() { 319 rwm := NewDRWMutex(ds, "test") 320 foo++ 321 if foo%writeRatio == 0 { 322 rwm.Lock(id, source) 323 rwm.Unlock(context.Background()) 324 } else { 325 rwm.RLock(id, source) 326 for i := 0; i != localWork; i++ { 327 foo *= 2 328 foo /= 2 329 } 330 rwm.RUnlock(context.Background()) 331 } 332 } 333 _ = foo 334 }) 335 } 336 337 // Borrowed from rwmutex_test.go 338 func BenchmarkRWMutexWrite100(b *testing.B) { 339 benchmarkRWMutex(b, 0, 100) 340 } 341 342 // Borrowed from rwmutex_test.go 343 func BenchmarkRWMutexWrite10(b *testing.B) { 344 benchmarkRWMutex(b, 0, 10) 345 } 346 347 // Borrowed from rwmutex_test.go 348 func BenchmarkRWMutexWorkWrite100(b *testing.B) { 349 benchmarkRWMutex(b, 100, 100) 350 } 351 352 // Borrowed from rwmutex_test.go 353 func BenchmarkRWMutexWorkWrite10(b *testing.B) { 354 benchmarkRWMutex(b, 100, 10) 355 }