codeberg.org/gruf/go-mutexes@v1.5.0/map_test.go (about)

     1  package mutexes_test
     2  
     3  import (
     4  	"strconv"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"codeberg.org/gruf/go-mutexes"
    10  )
    11  
    12  const hammerTestCount = 1000
    13  
    14  func TestMapMutex(t *testing.T) {
    15  	var (
    16  		mm mutexes.MutexMap
    17  
    18  		unlock func()
    19  
    20  		runlock1 func()
    21  		runlock2 func()
    22  		runlock3 func()
    23  	)
    24  
    25  	// Check that write lock of one
    26  	// key doesn't impede read locks.
    27  	shouldHappenWhile(func() {
    28  		unlock = mm.Lock("w-hello")
    29  	}, func() {
    30  		runlock1 = mm.RLock("r-hello")
    31  		runlock2 = mm.RLock("r-hello")
    32  		runlock3 = mm.RLock("r-hello")
    33  	})
    34  
    35  	unlock()
    36  	runlock1()
    37  	runlock2()
    38  	runlock3()
    39  
    40  	// Check we cannot get write lock twice.
    41  	shouldNotHappenWhile(func() {
    42  		unlock = mm.Lock("w-hello")
    43  	}, func() {
    44  		mm.Lock("w-hello")()
    45  	})
    46  
    47  	unlock()
    48  
    49  	// Check that write lock of
    50  	// key impedes read locks.
    51  	shouldNotHappenWhile(func() {
    52  		unlock = mm.Lock("hello")
    53  	}, func() {
    54  		mm.RLock("hello")()
    55  	})
    56  }
    57  
    58  func shouldHappenWhile(while func(), should func(), timeout ...time.Duration) {
    59  	sleep := time.Second
    60  
    61  	if len(timeout) > 0 {
    62  		sleep = timeout[0]
    63  	}
    64  
    65  	ch := make(chan struct{})
    66  
    67  	while()
    68  
    69  	go func() {
    70  		should()
    71  		close(ch)
    72  	}()
    73  
    74  	select {
    75  	case <-ch:
    76  	case <-time.After(sleep):
    77  		panic("timed out")
    78  	}
    79  }
    80  
    81  func shouldNotHappenWhile(while func(), shouldNot func(), timeout ...time.Duration) {
    82  	sleep := time.Second
    83  
    84  	if len(timeout) > 0 {
    85  		sleep = timeout[0]
    86  	}
    87  
    88  	ch := make(chan struct{})
    89  
    90  	while()
    91  
    92  	go func() {
    93  		shouldNot()
    94  		close(ch)
    95  	}()
    96  
    97  	time.Sleep(sleep)
    98  
    99  	select {
   100  	case <-ch:
   101  		panic("should not have happened")
   102  	default:
   103  	}
   104  }
   105  
   106  func TestMapMutexHammer(t *testing.T) {
   107  	var mm mutexes.MutexMap
   108  
   109  	wg := sync.WaitGroup{}
   110  	for i := 0; i < hammerTestCount; i++ {
   111  		go func(i int) {
   112  			key := strconv.Itoa(i)
   113  
   114  			// Get the original starting lock
   115  			runlockBase := mm.RLock(key)
   116  
   117  			// Perform slow async unlock
   118  			wg.Add(1)
   119  			go func() {
   120  				time.Sleep(time.Second)
   121  				runlockBase()
   122  				wg.Done()
   123  			}()
   124  
   125  			// Perform a quick write lock
   126  			mm.Lock(key)()
   127  
   128  			for j := 0; j < hammerTestCount; j++ {
   129  				runlock := mm.RLock(key)
   130  
   131  				// Perform slow async unlock
   132  				wg.Add(1)
   133  				go func() {
   134  					time.Sleep(time.Millisecond * 10)
   135  					runlock()
   136  					wg.Done()
   137  				}()
   138  			}
   139  
   140  			// Start a waiting write lock
   141  			unlock := mm.Lock(key)
   142  			unlock()
   143  		}(i)
   144  	}
   145  	time.Sleep(time.Second)
   146  	wg.Wait()
   147  }