github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/sync2/biasedmutex_test.go (about)

     1  package sync2
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"sync"
     7  	"sync/atomic"
     8  	"testing"
     9  )
    10  
    11  func TestBiasedMutexProgress(t *testing.T) {
    12  	for _, rt := range []int{1, 2, 4, 8} {
    13  		for _, wt := range []int{1, 2, 4, 8} {
    14  			name := fmt.Sprintf("r%vw%v", rt, wt)
    15  
    16  			t.Run(name, func(t *testing.T) {
    17  				m := NewBiasedMutex()
    18  				m.SetReaderThreshold(rt)
    19  				m.SetWriterThreshold(wt)
    20  
    21  				testProgress(t, m)
    22  			})
    23  		}
    24  	}
    25  }
    26  
    27  func TestStandardRWMutex(t *testing.T) {
    28  	testProgress(t, &sync.RWMutex{})
    29  }
    30  
    31  type RWMutex interface {
    32  	Lock()
    33  	Unlock()
    34  	RLock()
    35  	RUnlock()
    36  }
    37  
    38  func testProgress(t *testing.T, m RWMutex) {
    39  	const STEPS = 10000
    40  	const R, W = 100, 100
    41  
    42  	var totalProgress int64
    43  	var readerProgress int64
    44  	var writerProgress int64
    45  
    46  	var sharedVariable int
    47  
    48  	var activeWriters int64
    49  	var activeReaders int64
    50  
    51  	var wg sync.WaitGroup
    52  
    53  	var mufatal sync.Mutex
    54  	var fatals []string
    55  	fatal := func(err string) {
    56  		mufatal.Lock()
    57  		fatals = append(fatals, err)
    58  		mufatal.Unlock()
    59  		atomic.AddInt64(&totalProgress, STEPS)
    60  	}
    61  
    62  	order := make([]byte, STEPS)
    63  
    64  	wg.Add(R)
    65  	for i := 0; i < R; i++ {
    66  		go func() {
    67  			defer wg.Done()
    68  
    69  			localTotal := 0
    70  			for {
    71  				m.RLock()
    72  
    73  				orderx := atomic.AddInt64(&totalProgress, 1)
    74  				if orderx >= STEPS {
    75  					m.RUnlock()
    76  					break
    77  				}
    78  				order[orderx-1] = 'R'
    79  
    80  				atomic.AddInt64(&activeReaders, 1)
    81  				if atomic.LoadInt64(&activeWriters) > 0 {
    82  					fatal("writer active during reading")
    83  					atomic.AddInt64(&activeReaders, -1)
    84  					m.RUnlock()
    85  					break
    86  				}
    87  				atomic.AddInt64(&readerProgress, 1)
    88  				localTotal += sharedVariable
    89  				atomic.AddInt64(&activeReaders, -1)
    90  				m.RUnlock()
    91  			}
    92  		}()
    93  	}
    94  
    95  	wg.Add(W)
    96  	for i := 0; i < W; i++ {
    97  		go func(i int) {
    98  			defer wg.Done()
    99  
   100  			step := 0
   101  			for {
   102  				m.Lock()
   103  
   104  				orderx := atomic.AddInt64(&totalProgress, 1)
   105  				if orderx >= STEPS {
   106  					m.Unlock()
   107  					break
   108  				}
   109  				order[orderx-1] = 'W'
   110  
   111  				if atomic.AddInt64(&activeWriters, 1) > 1 {
   112  					fatal("more than one writer")
   113  					atomic.AddInt64(&activeWriters, -1)
   114  					m.Unlock()
   115  					break
   116  				}
   117  				if atomic.LoadInt64(&activeReaders) > 0 {
   118  					fatal("reader active during writing")
   119  					atomic.AddInt64(&activeWriters, -1)
   120  					m.Unlock()
   121  					break
   122  				}
   123  				writerProgress++
   124  				sharedVariable += step * i
   125  				step++
   126  				atomic.AddInt64(&activeWriters, -1)
   127  				m.Unlock()
   128  			}
   129  		}(i)
   130  	}
   131  
   132  	wg.Wait()
   133  
   134  	if len(fatals) > 0 {
   135  		t.Fatal("invalid semantics\n" + strings.Join(fatals, "\n"))
   136  	}
   137  
   138  	if readerProgress == 0 || writerProgress == 0 {
   139  		t.Fatalf("no progress reader:%v writer:%v", readerProgress, writerProgress)
   140  	}
   141  	x := STEPS
   142  	if x > 500 {
   143  		x = 500
   144  	}
   145  	t.Logf("reader:%v writer:%v\n%v", readerProgress, writerProgress, string(order[:x]))
   146  }