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  }