Files
tinygo/src/sync/mutex_test.go
T
Nia Waldvogel 38305399a3 sync: add tests
2021-12-22 11:02:45 +01:00

206 lines
4.1 KiB
Go

package sync_test
import (
"runtime"
"sync"
"sync/atomic"
"testing"
)
// TestMutexUncontended tests locking and unlocking a Mutex that is not shared with any other goroutines.
func TestMutexUncontended(t *testing.T) {
var mu sync.Mutex
// Lock and unlock the mutex a few times.
for i := 0; i < 3; i++ {
mu.Lock()
mu.Unlock()
}
}
// TestMutexConcurrent tests a mutex concurrently from multiple goroutines.
// It will fail if multiple goroutines hold the lock simultaneously.
func TestMutexConcurrent(t *testing.T) {
var mu sync.Mutex
var active uint
var completed uint
ok := true
const n = 10
for i := 0; i < n; i++ {
j := i
go func() {
// Delay a bit.
for k := j; k > 0; k-- {
runtime.Gosched()
}
mu.Lock()
// Increment the active counter.
active++
if active > 1 {
// Multiple things are holding the lock at the same time.
ok = false
} else {
// Delay a bit.
for k := j; k < n; k++ {
runtime.Gosched()
}
}
// Decrement the active counter.
active--
// This is completed.
completed++
mu.Unlock()
}()
}
// Wait for everything to finish.
var done bool
for !done {
// Wait a bit for other things to run.
runtime.Gosched()
// Acquire the lock and check whether everything has completed.
mu.Lock()
done = completed == n
mu.Unlock()
}
if !ok {
t.Error("lock held concurrently")
}
}
// TestRWMutexUncontended tests locking and unlocking an RWMutex that is not shared with any other goroutines.
func TestRWMutexUncontended(t *testing.T) {
var mu sync.RWMutex
// Lock the mutex exclusively and then unlock it.
mu.Lock()
mu.Unlock()
// Acuire several read locks.
const n = 5
for i := 0; i < n; i++ {
mu.RLock()
}
// Release all of the read locks.
for i := 0; i < n; i++ {
mu.RUnlock()
}
// Re-acquire the lock exclusively.
mu.Lock()
mu.Unlock()
}
// TestRWMutexWriteToRead tests the transition from a write lock to a read lock while contended.
func TestRWMutexWriteToRead(t *testing.T) {
// Create a new RWMutex and acquire a write lock.
var mu sync.RWMutex
mu.Lock()
const n = 3
var readAcquires uint32
var completed uint32
var unlocked uint32
var bad uint32
for i := 0; i < n; i++ {
go func() {
// Acquire a read lock.
mu.RLock()
// Verify that the write lock is supposed to be released by now.
if atomic.LoadUint32(&unlocked) == 0 {
// The write lock is still being held.
atomic.AddUint32(&bad, 1)
}
// Add ourselves to the read lock counter.
atomic.AddUint32(&readAcquires, 1)
// Wait for everything to hold the read lock simultaneously.
for atomic.LoadUint32(&readAcquires) < n {
runtime.Gosched()
}
// Notify of completion.
atomic.AddUint32(&completed, 1)
// Release the read lock.
mu.RUnlock()
}()
}
// Wait a bit for the goroutines to block.
for i := 0; i < 3*n; i++ {
runtime.Gosched()
}
// Release the write lock so that the goroutines acquire read locks.
atomic.StoreUint32(&unlocked, 1)
mu.Unlock()
// Wait for everything to complete.
for atomic.LoadUint32(&completed) < n {
runtime.Gosched()
}
// Acquire another write lock.
mu.Lock()
if bad != 0 {
t.Error("read lock acquired while write-locked")
}
}
// TestRWMutexWriteToRead tests the transition from a read lock to a write lock while contended.
func TestRWMutexReadToWrite(t *testing.T) {
// Create a new RWMutex and read-lock it several times.
const n = 3
var mu sync.RWMutex
var readers uint32
for i := 0; i < n; i++ {
mu.RLock()
readers++
}
// Start a goroutine to acquire a write lock.
result := ^uint32(0)
go func() {
// Acquire a write lock.
mu.Lock()
// Check for active readers.
readers := atomic.LoadUint32(&readers)
mu.Unlock()
// Report the number of active readers.
atomic.StoreUint32(&result, readers)
}()
// Release the read locks.
for i := 0; i < n; i++ {
runtime.Gosched()
atomic.AddUint32(&readers, ^uint32(0))
mu.RUnlock()
}
// Wait for a result.
var res uint32
for res == ^uint32(0) {
runtime.Gosched()
res = atomic.LoadUint32(&result)
}
if res != 0 {
t.Errorf("write lock acquired while %d readers were active", res)
}
}