github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/sync/mutex_test.go (about) 1 package sync_test 2 3 import ( 4 "runtime" 5 "sync" 6 "sync/atomic" 7 "testing" 8 ) 9 10 func HammerMutex(m *sync.Mutex, loops int, cdone chan bool) { 11 for i := 0; i < loops; i++ { 12 if i%3 == 0 { 13 if m.TryLock() { 14 m.Unlock() 15 } 16 continue 17 } 18 m.Lock() 19 m.Unlock() 20 } 21 cdone <- true 22 } 23 24 func TestMutex(t *testing.T) { 25 m := new(sync.Mutex) 26 27 m.Lock() 28 if m.TryLock() { 29 t.Fatalf("TryLock succeeded with mutex locked") 30 } 31 m.Unlock() 32 if !m.TryLock() { 33 t.Fatalf("TryLock failed with mutex unlocked") 34 } 35 m.Unlock() 36 37 c := make(chan bool) 38 for i := 0; i < 10; i++ { 39 go HammerMutex(m, 1000, c) 40 } 41 for i := 0; i < 10; i++ { 42 <-c 43 } 44 } 45 46 // TestMutexUncontended tests locking and unlocking a Mutex that is not shared with any other goroutines. 47 func TestMutexUncontended(t *testing.T) { 48 var mu sync.Mutex 49 50 // Lock and unlock the mutex a few times. 51 for i := 0; i < 3; i++ { 52 mu.Lock() 53 mu.Unlock() 54 } 55 } 56 57 // TestMutexConcurrent tests a mutex concurrently from multiple goroutines. 58 // It will fail if multiple goroutines hold the lock simultaneously. 59 func TestMutexConcurrent(t *testing.T) { 60 var mu sync.Mutex 61 var active uint 62 var completed uint 63 ok := true 64 65 const n = 10 66 for i := 0; i < n; i++ { 67 j := i 68 go func() { 69 // Delay a bit. 70 for k := j; k > 0; k-- { 71 runtime.Gosched() 72 } 73 74 mu.Lock() 75 76 // Increment the active counter. 77 active++ 78 79 if active > 1 { 80 // Multiple things are holding the lock at the same time. 81 ok = false 82 } else { 83 // Delay a bit. 84 for k := j; k < n; k++ { 85 runtime.Gosched() 86 } 87 } 88 89 // Decrement the active counter. 90 active-- 91 92 // This is completed. 93 completed++ 94 95 mu.Unlock() 96 }() 97 } 98 99 // Wait for everything to finish. 100 var done bool 101 for !done { 102 // Wait a bit for other things to run. 103 runtime.Gosched() 104 105 // Acquire the lock and check whether everything has completed. 106 mu.Lock() 107 done = completed == n 108 mu.Unlock() 109 } 110 if !ok { 111 t.Error("lock held concurrently") 112 } 113 } 114 115 // TestRWMutexUncontended tests locking and unlocking an RWMutex that is not shared with any other goroutines. 116 func TestRWMutexUncontended(t *testing.T) { 117 var mu sync.RWMutex 118 119 // Lock the mutex exclusively and then unlock it. 120 mu.Lock() 121 mu.Unlock() 122 123 // Acuire several read locks. 124 const n = 5 125 for i := 0; i < n; i++ { 126 mu.RLock() 127 } 128 129 // Release all of the read locks. 130 for i := 0; i < n; i++ { 131 mu.RUnlock() 132 } 133 134 // Re-acquire the lock exclusively. 135 mu.Lock() 136 mu.Unlock() 137 } 138 139 // TestRWMutexWriteToRead tests the transition from a write lock to a read lock while contended. 140 func TestRWMutexWriteToRead(t *testing.T) { 141 // Create a new RWMutex and acquire a write lock. 142 var mu sync.RWMutex 143 mu.Lock() 144 145 const n = 3 146 var readAcquires uint32 147 var completed uint32 148 var unlocked uint32 149 var bad uint32 150 for i := 0; i < n; i++ { 151 go func() { 152 // Acquire a read lock. 153 mu.RLock() 154 155 // Verify that the write lock is supposed to be released by now. 156 if atomic.LoadUint32(&unlocked) == 0 { 157 // The write lock is still being held. 158 atomic.AddUint32(&bad, 1) 159 } 160 161 // Add ourselves to the read lock counter. 162 atomic.AddUint32(&readAcquires, 1) 163 164 // Wait for everything to hold the read lock simultaneously. 165 for atomic.LoadUint32(&readAcquires) < n { 166 runtime.Gosched() 167 } 168 169 // Notify of completion. 170 atomic.AddUint32(&completed, 1) 171 172 // Release the read lock. 173 mu.RUnlock() 174 }() 175 } 176 177 // Wait a bit for the goroutines to block. 178 for i := 0; i < 3*n; i++ { 179 runtime.Gosched() 180 } 181 182 // Release the write lock so that the goroutines acquire read locks. 183 atomic.StoreUint32(&unlocked, 1) 184 mu.Unlock() 185 186 // Wait for everything to complete. 187 for atomic.LoadUint32(&completed) < n { 188 runtime.Gosched() 189 } 190 191 // Acquire another write lock. 192 mu.Lock() 193 194 if bad != 0 { 195 t.Error("read lock acquired while write-locked") 196 } 197 } 198 199 // TestRWMutexWriteToRead tests the transition from a read lock to a write lock while contended. 200 func TestRWMutexReadToWrite(t *testing.T) { 201 // Create a new RWMutex and read-lock it several times. 202 const n = 3 203 var mu sync.RWMutex 204 var readers uint32 205 for i := 0; i < n; i++ { 206 mu.RLock() 207 readers++ 208 } 209 210 // Start a goroutine to acquire a write lock. 211 result := ^uint32(0) 212 go func() { 213 // Acquire a write lock. 214 mu.Lock() 215 216 // Check for active readers. 217 readers := atomic.LoadUint32(&readers) 218 219 mu.Unlock() 220 221 // Report the number of active readers. 222 atomic.StoreUint32(&result, readers) 223 }() 224 225 // Release the read locks. 226 for i := 0; i < n; i++ { 227 runtime.Gosched() 228 atomic.AddUint32(&readers, ^uint32(0)) 229 mu.RUnlock() 230 } 231 232 // Wait for a result. 233 var res uint32 234 for res == ^uint32(0) { 235 runtime.Gosched() 236 res = atomic.LoadUint32(&result) 237 } 238 if res != 0 { 239 t.Errorf("write lock acquired while %d readers were active", res) 240 } 241 }