github.com/haraldrudell/parl@v0.4.176/counter/access-manager.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package counter 7 8 import ( 9 "math" 10 "sync" 11 "sync/atomic" 12 ) 13 14 const ( 15 lockAccessBit = uint64(1) 16 ticketDelta = uint64(2) 17 negativeTicketDelta = math.MaxUint64 - ticketDelta + 1 18 atomicsComplete = lockAccessBit 19 ) 20 21 // accessManager controls atomic and lock-based access 22 // - Lock access: defer a.Lock().Unlock() 23 // - atomic or lock access: defer RelinquishAccess(RequestAccess()) 24 type accessManager struct { 25 // lock for data access used during lock access periods 26 accessLock sync.Mutex 27 28 // number of requestors requiring lock use, atomic 29 lockers uint64 30 // lock for changing atomic access status 31 // - ensures sequential by-single-thread transitions of lockAccessBit 32 // - reduces [accessManager.before] contention 33 controlLock sync.Mutex 34 // atomic access ongoing operations counter, atomic 35 // - lowest bit is lockAccessBit 36 // - lockAccessBit set and cleared behind controlLock 37 before uint64 38 } 39 40 // RequestAccess allows a data provider to make writes to 41 // shared data without knowing if access is atomic or behind lock 42 // 43 // Usage: 44 // 45 // func f() { 46 // defer RelinquishAccess(RequestAccess()) 47 // … 48 func (a *accessManager) RequestAccess() (isLockAccess bool) { 49 50 // deteremine access method 51 for { 52 var before = atomic.LoadUint64(&a.before) 53 isLockAccess = before&lockAccessBit != 0 54 // attempt atomic access 55 if !isLockAccess { 56 if atomic.CompareAndSwapUint64(&a.before, before, before+ticketDelta) { 57 return // atomic access return: isLockAccess: false 58 } 59 continue // try again 60 } 61 62 // it is lock access 63 // acquire access lock 64 a.accessLock.Lock() 65 return // lock acquired return: isLockAccess true 66 } 67 } 68 69 // RelinquishAccess signals that a provider is done writing to 70 // shared data 71 func (a *accessManager) RelinquishAccess(isLockAccess bool) { 72 if isLockAccess { 73 // end lock access 74 a.accessLock.Unlock() 75 return 76 } 77 78 // end atomic access 79 atomic.AddUint64(&a.before, negativeTicketDelta) 80 } 81 82 // Lock sets access behind lock, clears out atomic operations and 83 // acquires the access-lock 84 // 85 // Usage: 86 // 87 // func f() { 88 // defer a.Lock().Unlock() 89 // … 90 func (a *accessManager) Lock() (a2 *accessManager) { 91 a2 = a 92 var before = a.disableAtomic() 93 // ensure atomic operations ceased 94 for before != atomicsComplete { 95 before = atomic.LoadUint64(&a.before) 96 } 97 // acquire lock 98 a.accessLock.Lock() 99 return 100 } 101 102 // Unlock releases the access-lock 103 func (a *accessManager) Unlock() { 104 a.enableAtomic() 105 a.accessLock.Unlock() 106 } 107 108 // disableAtomic transitions to lock-access 109 // - on return lock access is set and atomic operations have ceased 110 func (a *accessManager) disableAtomic() (before uint64) { 111 112 // prevent lockRequestBit from being cleared 113 // - a.lockers > 0 114 atomic.AddUint64(&a.lockers, 1) 115 // - check current state 116 if before = atomic.LoadUint64(&a.before); before&lockAccessBit != 0 { 117 return // bit set but atomic operations may be ongoing return 118 } 119 a.controlLock.Lock() 120 defer a.controlLock.Unlock() 121 122 // ensure lockAccessBit true 123 // - check current state 124 if before = atomic.LoadUint64(&a.before); before&lockAccessBit != 0 { 125 return // bit set but atomic operations may be ongoing return 126 } 127 128 // set lockAccessBit 129 for { 130 var oldBefore = atomic.LoadUint64(&a.before) 131 before = oldBefore | lockAccessBit 132 if atomic.CompareAndSwapUint64(&a.before, oldBefore, before) { 133 return // bit set but atomic operations may be ongoing return 134 } 135 } 136 } 137 138 // enableAtomic ends a lock-access period which may re-enable atomic access 139 func (a *accessManager) enableAtomic() { 140 141 // decrement and check if atomic access should be re-enabled 142 if atomic.AddUint64(&a.lockers, ^uint64(0)) > 0 { 143 return // do not re-enable atomic access: pending lock-use requestors return 144 } 145 // check if bit already cleared 146 if atomic.LoadUint64(&a.before)&lockAccessBit == 0 { 147 return // bit already cleared return 148 } 149 a.controlLock.Lock() 150 defer a.controlLock.Unlock() 151 152 // clear lock-access bit 153 // check if bit already cleared 154 if atomic.LoadUint64(&a.before)&lockAccessBit == 0 { 155 return // bit already cleared return 156 } 157 for { 158 159 // if pending Lock invocation: abort 160 if atomic.LoadUint64(&a.lockers) > 0 { 161 return // no re-enable: more requestors return 162 } 163 164 // attempt to clear bit 165 var before = atomic.LoadUint64(&a.before) 166 if atomic.CompareAndSwapUint64(&a.before, before, before&^lockAccessBit) { 167 return // bit was cleared return 168 } 169 } 170 }