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 }