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  }