github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/sync/seqcount.go (about)

     1  // Copyright 2019 The gVisor Authors.
     2  //
     3  // Use of this source code is governed by a BSD-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package sync
     7  
     8  import (
     9  	"sync/atomic"
    10  )
    11  
    12  // SeqCount is a synchronization primitive for optimistic reader/writer
    13  // synchronization in cases where readers can work with stale data and
    14  // therefore do not need to block writers.
    15  //
    16  // Compared to sync/atomic.Value:
    17  //
    18  //   - Mutation of SeqCount-protected data does not require memory allocation,
    19  //     whereas atomic.Value generally does. This is a significant advantage when
    20  //     writes are common.
    21  //
    22  //   - Atomic reads of SeqCount-protected data require copying. This is a
    23  //     disadvantage when atomic reads are common.
    24  //
    25  //   - SeqCount may be more flexible: correct use of SeqCount.ReadOk allows other
    26  //     operations to be made atomic with reads of SeqCount-protected data.
    27  //
    28  //   - SeqCount is more cumbersome to use; atomic reads of SeqCount-protected
    29  //     data require instantiating function templates using go_generics (see
    30  //     seqatomic.go).
    31  type SeqCount struct {
    32  	// epoch is incremented by BeginWrite and EndWrite, such that epoch is odd
    33  	// if a writer critical section is active, and a read from data protected
    34  	// by this SeqCount is atomic iff epoch is the same even value before and
    35  	// after the read.
    36  	epoch uint32
    37  }
    38  
    39  // SeqCountEpoch tracks writer critical sections in a SeqCount.
    40  type SeqCountEpoch uint32
    41  
    42  // We assume that:
    43  //
    44  //	- All functions in sync/atomic that perform a memory read are at least a
    45  //		read fence: memory reads before calls to such functions cannot be reordered
    46  //		after the call, and memory reads after calls to such functions cannot be
    47  //		reordered before the call, even if those reads do not use sync/atomic.
    48  //
    49  //	- All functions in sync/atomic that perform a memory write are at least a
    50  //		write fence: memory writes before calls to such functions cannot be
    51  //		reordered after the call, and memory writes after calls to such functions
    52  //		cannot be reordered before the call, even if those writes do not use
    53  //		sync/atomic.
    54  //
    55  // As of this writing, the Go memory model completely fails to describe
    56  // sync/atomic, but these properties are implied by
    57  // https://groups.google.com/forum/#!topic/golang-nuts/7EnEhM3U7B8.
    58  
    59  // BeginRead indicates the beginning of a reader critical section. Reader
    60  // critical sections DO NOT BLOCK writer critical sections, so operations in a
    61  // reader critical section MAY RACE with writer critical sections. Races are
    62  // detected by ReadOk at the end of the reader critical section. Thus, the
    63  // low-level structure of readers is generally:
    64  //
    65  //	for {
    66  //	    epoch := seq.BeginRead()
    67  //	    // do something idempotent with seq-protected data
    68  //	    if seq.ReadOk(epoch) {
    69  //	        break
    70  //	    }
    71  //	}
    72  //
    73  // However, since reader critical sections may race with writer critical
    74  // sections, the Go race detector will (accurately) flag data races in readers
    75  // using this pattern. Most users of SeqCount will need to use the
    76  // SeqAtomicLoad function template in seqatomic.go.
    77  func (s *SeqCount) BeginRead() SeqCountEpoch {
    78  	if epoch := atomic.LoadUint32(&s.epoch); epoch&1 == 0 {
    79  		return SeqCountEpoch(epoch)
    80  	}
    81  	return s.beginReadSlow()
    82  }
    83  
    84  func (s *SeqCount) beginReadSlow() SeqCountEpoch {
    85  	i := 0
    86  	for {
    87  		if canSpin(i) {
    88  			i++
    89  			doSpin()
    90  		} else {
    91  			goyield()
    92  		}
    93  		if epoch := atomic.LoadUint32(&s.epoch); epoch&1 == 0 {
    94  			return SeqCountEpoch(epoch)
    95  		}
    96  	}
    97  }
    98  
    99  // ReadOk returns true if the reader critical section initiated by a previous
   100  // call to BeginRead() that returned epoch did not race with any writer critical
   101  // sections.
   102  //
   103  // ReadOk may be called any number of times during a reader critical section.
   104  // Reader critical sections do not need to be explicitly terminated; the last
   105  // call to ReadOk is implicitly the end of the reader critical section.
   106  func (s *SeqCount) ReadOk(epoch SeqCountEpoch) bool {
   107  	return atomic.LoadUint32(&s.epoch) == uint32(epoch)
   108  }
   109  
   110  // BeginWrite indicates the beginning of a writer critical section.
   111  //
   112  // SeqCount does not support concurrent writer critical sections; clients with
   113  // concurrent writers must synchronize them using e.g. sync.Mutex.
   114  func (s *SeqCount) BeginWrite() {
   115  	if epoch := atomic.AddUint32(&s.epoch, 1); epoch&1 == 0 {
   116  		panic("SeqCount.BeginWrite during writer critical section")
   117  	}
   118  }
   119  
   120  // BeginWriteOk combines the semantics of ReadOk and BeginWrite. If the reader
   121  // critical section initiated by a previous call to BeginRead() that returned
   122  // epoch did not race with any writer critical sections, it begins a writer
   123  // critical section and returns true. Otherwise it does nothing and returns
   124  // false.
   125  func (s *SeqCount) BeginWriteOk(epoch SeqCountEpoch) bool {
   126  	return atomic.CompareAndSwapUint32(&s.epoch, uint32(epoch), uint32(epoch)+1)
   127  }
   128  
   129  // EndWrite ends the effect of a preceding BeginWrite or successful
   130  // BeginWriteOk.
   131  func (s *SeqCount) EndWrite() {
   132  	if epoch := atomic.AddUint32(&s.epoch, 1); epoch&1 != 0 {
   133  		panic("SeqCount.EndWrite outside writer critical section")
   134  	}
   135  }