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  }