github.com/aclements/go-misc@v0.0.0-20240129233631-2f6ede80790c/go-weave/models/rwmutex.go (about)

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build ignore
     6  
     7  // rwmutex is a model of runtime.rwmutex.
     8  package main
     9  
    10  import (
    11  	"github.com/aclements/go-misc/go-weave/amb"
    12  	"github.com/aclements/go-misc/go-weave/weave"
    13  )
    14  
    15  //var sched = weave.Scheduler{Strategy: &amb.StrategyRandom{}}
    16  var sched = weave.Scheduler{Strategy: &amb.StrategyDFS{}}
    17  
    18  const verbose = false
    19  
    20  func main() {
    21  	sched.Run(func() {
    22  		if verbose {
    23  			print("start:")
    24  		}
    25  		var rw rwmutex
    26  		for i := 0; i < 2; i++ {
    27  			sched.Go(func() {
    28  				rw.lock()
    29  				rw.unlock()
    30  			})
    31  		}
    32  		for i := 0; i < 2; i++ {
    33  			sched.Go(func() {
    34  				rw.rlock()
    35  				rw.runlock()
    36  			})
    37  		}
    38  	})
    39  }
    40  
    41  func atomicXadd(x *uint32, delta int32) uint32 {
    42  	*x += uint32(delta)
    43  	r := *x
    44  	sched.Sched()
    45  	return r
    46  }
    47  
    48  func atomicLoad(x *uint32) uint32 {
    49  	r := *x
    50  	sched.Sched()
    51  	return r
    52  }
    53  
    54  func lock(m *weave.Mutex) {
    55  	m.Lock()
    56  }
    57  
    58  func unlock(m *weave.Mutex) {
    59  	m.Unlock()
    60  }
    61  
    62  type m struct {
    63  	schedlink muintptr
    64  	park      weave.Semaphore
    65  }
    66  
    67  type g struct {
    68  	m *m
    69  }
    70  
    71  var curG = weave.NewTLS()
    72  
    73  func notesleep(s *weave.Semaphore) {
    74  	s.Acquire(1)
    75  }
    76  
    77  func notewakeup(s *weave.Semaphore) {
    78  	s.Release(1)
    79  }
    80  
    81  func noteclear(s *weave.Semaphore) {
    82  }
    83  
    84  func getg() *g {
    85  	gp, ok := curG.Get().(*g)
    86  	if !ok {
    87  		gp = &g{&m{}}
    88  		curG.Set(gp)
    89  	}
    90  	return gp
    91  }
    92  
    93  type rwmutex struct {
    94  	rLock      weave.Mutex // protects readers, readerPass, writer
    95  	readers    muintptr    // list of pending readers
    96  	readerPass uint32      // number of readers to skip readers list
    97  
    98  	wLock  weave.Mutex // serializes writers
    99  	writer muintptr    // pending writer waiting for completing readers
   100  
   101  	readerCount uint32 // number of pending readers
   102  	readerWait  uint32 // number of departing readers
   103  
   104  	// Self-checking
   105  	checkReaders uint32
   106  	checkWriters uint32
   107  }
   108  
   109  type muintptr struct {
   110  	mp *m
   111  }
   112  
   113  func (mp *muintptr) set(x *m) {
   114  	mp.mp = x
   115  }
   116  
   117  func (mp *muintptr) ptr() *m {
   118  	return mp.mp
   119  }
   120  
   121  func systemstack(x func()) {
   122  	x()
   123  }
   124  
   125  func throw(x string) {
   126  	panic(x)
   127  }
   128  
   129  const rwmutexMaxReaders = 1 << 30
   130  
   131  // rlock locks rw for reading.
   132  func (rw *rwmutex) rlock() {
   133  	sched.Tracef("rw.readerCount (%d) += 1", rw.readerCount)
   134  	if int32(atomicXadd(&rw.readerCount, 1)) < 0 {
   135  		// A writer is pending. Park on the reader queue.
   136  		sched.Trace("writer pending")
   137  		systemstack(func() {
   138  			lock(&rw.rLock)
   139  			// Writer may have released while we were
   140  			// getting the lock.
   141  			sched.Trace("got rLock")
   142  			if rw.readerPass > 0 {
   143  				// Writer finished.
   144  				rw.readerPass -= 1
   145  				unlock(&rw.rLock)
   146  			} else {
   147  				// Queue this reader to be woken by
   148  				// the writer.
   149  				m := getg().m
   150  				m.schedlink = rw.readers
   151  				rw.readers.set(m)
   152  				sched.Trace("reader queued")
   153  				unlock(&rw.rLock)
   154  				notesleep(&m.park)
   155  				noteclear(&m.park)
   156  			}
   157  		})
   158  	}
   159  
   160  	// Self-check
   161  	if rw.checkWriters != 0 {
   162  		panic("rlock with writers")
   163  	}
   164  	rw.checkReaders++
   165  }
   166  
   167  // runlock undoes a single rlock call on rw.
   168  func (rw *rwmutex) runlock() {
   169  	if rw.checkReaders <= 0 {
   170  		panic("runlock with no readers")
   171  	}
   172  	if rw.checkWriters != 0 {
   173  		panic("runlock with writers")
   174  	}
   175  	rw.checkReaders--
   176  
   177  	sched.Tracef("rw.readerCount (%d) -= 1", rw.readerCount)
   178  	if r := int32(atomicXadd(&rw.readerCount, -1)); r < 0 {
   179  		sched.Tracef("r = %d", r)
   180  		if r+1 == 0 || r+1 == -rwmutexMaxReaders {
   181  			throw("runlock of unlocked rwmutex")
   182  		}
   183  		// A writer is pending.
   184  		sched.Tracef("rw.readerWait (%d) -= 1", rw.readerWait)
   185  		if atomicXadd(&rw.readerWait, -1) == 0 {
   186  			// The last reader unblocks the writer.
   187  			sched.Trace("last reader")
   188  			lock(&rw.rLock)
   189  			w := rw.writer.ptr()
   190  			if w != nil {
   191  				sched.Trace("wake writer")
   192  				notewakeup(&w.park)
   193  			}
   194  			unlock(&rw.rLock)
   195  		}
   196  	}
   197  }
   198  
   199  // lock locks rw for writing.
   200  func (rw *rwmutex) lock() {
   201  	// Resolve competition with other writers.
   202  	lock(&rw.wLock)
   203  	sched.Trace("got wLock")
   204  	m := getg().m
   205  	// Announce that there is a pending writer.
   206  	sched.Tracef("rw.readerCount (%d) -= rwmutexMaxReaders", rw.readerCount)
   207  	r := int32(atomicXadd(&rw.readerCount, -rwmutexMaxReaders)) + rwmutexMaxReaders
   208  	// Wait for any active readers to complete.
   209  	lock(&rw.rLock) // NEW
   210  	if r != 0 {
   211  		sched.Tracef("rw.readerWait (%d) += %d", rw.readerWait, r)
   212  	}
   213  	if r != 0 && atomicXadd(&rw.readerWait, r) != 0 {
   214  		sched.Trace("waiting for readers")
   215  		// Wait for reader to wake us up.
   216  		systemstack(func() {
   217  			rw.writer.set(m)
   218  			unlock(&rw.rLock) // NEW
   219  			notesleep(&m.park)
   220  			noteclear(&m.park)
   221  		})
   222  	} else {
   223  		sched.Trace("no readers")
   224  		unlock(&rw.rLock) // NEW
   225  	}
   226  
   227  	// Self-check
   228  	if rw.checkReaders != 0 {
   229  		panic("lock with readers")
   230  	}
   231  	if rw.checkWriters != 0 {
   232  		panic("lock with writers")
   233  	}
   234  	rw.checkWriters++
   235  }
   236  
   237  // unlock unlocks rw for writing.
   238  func (rw *rwmutex) unlock() {
   239  	// Self-check
   240  	if rw.checkReaders != 0 {
   241  		panic("unlock with readers")
   242  	}
   243  	if rw.checkWriters != 1 {
   244  		panic("unlock with wrong writers")
   245  	}
   246  	rw.checkWriters--
   247  
   248  	// Announce to readers that there is no active writer.
   249  	sched.Tracef("rw.readerCount (%d) += rwmutexMaxReaders", rw.readerCount)
   250  	r := int32(atomicXadd(&rw.readerCount, rwmutexMaxReaders))
   251  	if r >= rwmutexMaxReaders {
   252  		throw("unlock of unlocked rwmutex")
   253  	}
   254  	// Unblock blocked readers.
   255  	lock(&rw.rLock)
   256  	for rw.readers.ptr() != nil {
   257  		sched.Tracef("wake reader")
   258  		reader := rw.readers.ptr()
   259  		rw.readers = reader.schedlink
   260  		reader.schedlink.set(nil)
   261  		notewakeup(&reader.park)
   262  		r -= 1
   263  	}
   264  	// If r > 0, there are pending readers that aren't on the
   265  	// queue. Tell them to skip waiting.
   266  	rw.readerPass += uint32(r)
   267  	unlock(&rw.rLock)
   268  	// Allow other writers to proceed.
   269  	sched.Tracef("release wLock")
   270  	unlock(&rw.wLock)
   271  }