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 }