github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/lock/lock_test.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // GOMAXPROCS=10 go test
     6  
     7  package lock
     8  
     9  import (
    10  	"flag"
    11  	"fmt"
    12  	"runtime"
    13  	"sync/atomic"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/cozy/cozy-stack/pkg/prefixer"
    18  	"github.com/redis/go-redis/v9"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  // If you want to test harder the lock, you can set nb = 1000 but it is too
    24  // slow for CI, and the lock package has very few commits in the last years.
    25  var nb = 100
    26  
    27  func TestLock(t *testing.T) {
    28  	if testing.Short() {
    29  		t.Skip("an instance is required for this test: test skipped due to the use of --short flag")
    30  	}
    31  
    32  	flag.Parse()
    33  	if testing.Short() {
    34  		nb = 3
    35  	}
    36  
    37  	t.Run("MemLock", func(t *testing.T) {
    38  		client := NewInMemory()
    39  
    40  		db := prefixer.NewPrefixer(0, "cozy.local", "cozy.local")
    41  		l := client.ReadWrite(db, "test-mem")
    42  		hammerRW(t, l)
    43  	})
    44  
    45  	t.Run("RedisLock", func(t *testing.T) {
    46  		opt, err := redis.ParseURL("redis://localhost:6379/0")
    47  		require.NoError(t, err)
    48  		client := NewRedisLockGetter(redis.NewClient(opt))
    49  
    50  		db := prefixer.NewPrefixer(0, "cozy.local", "cozy.local")
    51  		l := client.ReadWrite(db, "test-redis")
    52  		l.(*redisLock).timeout = time.Second
    53  		l.(*redisLock).waitRetry = 100 * time.Millisecond
    54  
    55  		require.NoError(t, l.RLock())
    56  		require.NoError(t, l.RLock())
    57  		l.RUnlock()
    58  		require.Error(t, l.Lock())
    59  		l.RUnlock()
    60  		require.NoError(t, l.Lock())
    61  		l.Unlock()
    62  
    63  		hammerRW(t, l)
    64  
    65  		done := make(chan bool)
    66  		for i := 0; i < 10; i++ {
    67  			go HammerMutex(l, done)
    68  		}
    69  		for i := 0; i < 10; i++ {
    70  			<-done
    71  		}
    72  
    73  		other := client.ReadWrite(db, "test-redis")
    74  		assert.NoError(t, l.Lock())
    75  		assert.Error(t, other.Lock())
    76  
    77  		l.Unlock()
    78  	})
    79  
    80  	t.Run("LongLock", func(t *testing.T) {
    81  		if testing.Short() {
    82  			return
    83  		}
    84  
    85  		opt, err := redis.ParseURL("redis://localhost:6379/0")
    86  		require.NoError(t, err)
    87  		client := NewRedisLockGetter(redis.NewClient(opt))
    88  
    89  		db := prefixer.NewPrefixer(0, "cozy.local", "cozy.local")
    90  		long := client.LongOperation(db, "test-long")
    91  
    92  		// Reduce the default timeout duration.
    93  		long.(*longOperation).timeout = 50 * time.Millisecond
    94  
    95  		l := client.ReadWrite(db, "test-long")
    96  		l.(*redisLock).timeout = 200 * time.Millisecond
    97  		l.(*redisLock).waitRetry = 10 * time.Millisecond
    98  
    99  		// Take the lock and refresh it every 20ms without
   100  		// losing the lock
   101  		assert.NoError(t, long.Lock())
   102  
   103  		// Try a second lock. It should fail after 200ms, and the long lock
   104  		// will have been extended a few times in this span of time.
   105  		err = l.Lock()
   106  		assert.Error(t, err)
   107  		assert.Equal(t, ErrTooManyRetries, err)
   108  		long.Unlock()
   109  
   110  		// Check that a long lock can be reused
   111  		assert.NoError(t, long.Lock())
   112  		long.Unlock()
   113  	})
   114  }
   115  
   116  func reader(rwm ErrorRWLocker, iterations int, activity *int32, cdone chan bool) {
   117  	for i := 0; i < iterations; i++ {
   118  		err := rwm.RLock()
   119  		if err != nil {
   120  			panic(err)
   121  		}
   122  		n := atomic.AddInt32(activity, 1)
   123  		if n < 1 || n >= 10000 {
   124  			panic(fmt.Sprintf("wlock(%d)\n", n))
   125  		}
   126  		for i := 0; i < 100; i++ {
   127  		}
   128  		atomic.AddInt32(activity, -1)
   129  		rwm.RUnlock()
   130  	}
   131  	cdone <- true
   132  }
   133  
   134  func writer(rwm ErrorRWLocker, iterations int, activity *int32, cdone chan bool) {
   135  	for i := 0; i < iterations; i++ {
   136  		err := rwm.Lock()
   137  		if err != nil {
   138  			panic(err)
   139  		}
   140  		n := atomic.AddInt32(activity, 10000)
   141  		if n != 10000 {
   142  			panic(fmt.Sprintf("wlock(%d)\n", n))
   143  		}
   144  		for i := 0; i < 100; i++ {
   145  		}
   146  		atomic.AddInt32(activity, -10000)
   147  		rwm.Unlock()
   148  	}
   149  	cdone <- true
   150  }
   151  
   152  func HammerRWMutex(locker ErrorRWLocker, gomaxprocs, numReaders, iterations int) {
   153  	runtime.GOMAXPROCS(gomaxprocs)
   154  	// Number of active readers + 10000 * number of active writers.
   155  	var activity int32
   156  	cdone := make(chan bool)
   157  	go writer(locker, iterations, &activity, cdone)
   158  	var i int
   159  	for i = 0; i < numReaders/2; i++ {
   160  		go reader(locker, iterations, &activity, cdone)
   161  	}
   162  	go writer(locker, iterations, &activity, cdone)
   163  	for ; i < numReaders; i++ {
   164  		go reader(locker, iterations, &activity, cdone)
   165  	}
   166  	// Wait for the 2 writers and all readers to finish.
   167  	for i := 0; i < 2+numReaders; i++ {
   168  		<-cdone
   169  	}
   170  }
   171  
   172  func HammerMutex(m ErrorLocker, cdone chan bool) {
   173  	for i := 0; i < nb; i++ {
   174  		err := m.Lock()
   175  		if err != nil {
   176  			panic(err)
   177  		}
   178  		m.Unlock()
   179  	}
   180  	cdone <- true
   181  }
   182  
   183  func hammerRW(t *testing.T, l ErrorRWLocker) {
   184  	defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(-1))
   185  	HammerRWMutex(l, 1, 1, nb)
   186  	HammerRWMutex(l, 1, 3, nb)
   187  	HammerRWMutex(l, 1, 10, nb)
   188  	HammerRWMutex(l, 4, 1, nb)
   189  	HammerRWMutex(l, 4, 3, nb)
   190  	HammerRWMutex(l, 4, 10, nb)
   191  	HammerRWMutex(l, 10, 1, nb)
   192  	HammerRWMutex(l, 10, 3, nb)
   193  	HammerRWMutex(l, 10, 10, nb)
   194  	HammerRWMutex(l, 10, 5, nb)
   195  }