github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/cmd/namespace-lock_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 "runtime" 23 "testing" 24 "time" 25 ) 26 27 // WARNING: 28 // 29 // Expected source line number is hard coded, 35, in the 30 // following test. Adding new code before this test or changing its 31 // position will cause the line number to change and the test to FAIL 32 // Tests getSource(). 33 func TestGetSource(t *testing.T) { 34 currentSource := func() string { return getSource(2) } 35 gotSource := currentSource() 36 // Hard coded line number, 35, in the "expectedSource" value 37 expectedSource := "[namespace-lock_test.go:35:TestGetSource()]" 38 if gotSource != expectedSource { 39 t.Errorf("expected : %s, got : %s", expectedSource, gotSource) 40 } 41 } 42 43 // Test lock race 44 func TestNSLockRace(t *testing.T) { 45 t.Skip("long test skip it") 46 47 ctx := context.Background() 48 49 for i := 0; i < 10000; i++ { 50 nsLk := newNSLock(false) 51 52 // lk1; ref=1 53 if !nsLk.lock(ctx, "volume", "path", "source", "opsID", false, time.Second) { 54 t.Fatal("failed to acquire lock") 55 } 56 57 // lk2 58 lk2ch := make(chan struct{}) 59 go func() { 60 defer close(lk2ch) 61 nsLk.lock(ctx, "volume", "path", "source", "opsID", false, 1*time.Millisecond) 62 }() 63 time.Sleep(1 * time.Millisecond) // wait for goroutine to advance; ref=2 64 65 // Unlock the 1st lock; ref=1 after this line 66 nsLk.unlock("volume", "path", false) 67 68 // Taking another lockMapMutex here allows queuing up additional lockers. This should 69 // not be required but makes reproduction much easier. 70 nsLk.lockMapMutex.Lock() 71 72 // lk3 blocks. 73 lk3ch := make(chan bool) 74 go func() { 75 lk3ch <- nsLk.lock(ctx, "volume", "path", "source", "opsID", false, 0) 76 }() 77 78 // lk4, blocks. 79 lk4ch := make(chan bool) 80 go func() { 81 lk4ch <- nsLk.lock(ctx, "volume", "path", "source", "opsID", false, 0) 82 }() 83 runtime.Gosched() 84 85 // unlock the manual lock 86 nsLk.lockMapMutex.Unlock() 87 88 // To trigger the race: 89 // 1) lk3 or lk4 need to advance and increment the ref on the existing resource, 90 // successfully acquiring the lock. 91 // 2) lk2 then needs to advance and remove the resource from lockMap. 92 // 3) lk3 or lk4 (whichever didn't execute in step 1) then executes and creates 93 // a new entry in lockMap and acquires a lock for the same resource. 94 95 <-lk2ch 96 lk3ok := <-lk3ch 97 lk4ok := <-lk4ch 98 99 if lk3ok && lk4ok { 100 t.Fatalf("multiple locks acquired; iteration=%d, lk3=%t, lk4=%t", i, lk3ok, lk4ok) 101 } 102 } 103 }