github.com/nicocha30/gvisor-ligolo@v0.0.0-20230726075806-989fa2c0a413/pkg/syncevent/waiter_unsafe.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package syncevent 16 17 import ( 18 "sync/atomic" 19 "unsafe" 20 21 "github.com/nicocha30/gvisor-ligolo/pkg/sync" 22 ) 23 24 // Waiter allows a goroutine to block on pending events received by a Receiver. 25 // 26 // Waiter.Init() must be called before first use. 27 type Waiter struct { 28 r Receiver 29 30 // g is one of: 31 // 32 // - 0: No goroutine is blocking in Wait. 33 // 34 // - preparingG: A goroutine is in Wait preparing to sleep, but hasn't yet 35 // completed waiterUnlock(). Thus the wait can only be interrupted by 36 // replacing the value of g with 0 (the G may not be in state Gwaiting yet, 37 // so we can't call goready.) 38 // 39 // - Otherwise: g is a pointer to the runtime.g in state Gwaiting for the 40 // goroutine blocked in Wait, which can only be woken by calling goready. 41 g uintptr `state:"zerovalue"` 42 } 43 44 const preparingG = 1 45 46 // Init must be called before first use of w. 47 func (w *Waiter) Init() { 48 w.r.Init(w) 49 } 50 51 // Receiver returns the Receiver that receives events that unblock calls to 52 // w.Wait(). 53 func (w *Waiter) Receiver() *Receiver { 54 return &w.r 55 } 56 57 // Pending returns the set of pending events. 58 func (w *Waiter) Pending() Set { 59 return w.r.Pending() 60 } 61 62 // Wait blocks until at least one event is pending, then returns the set of 63 // pending events. It does not affect the set of pending events; callers must 64 // call w.Ack() to do so, or use w.WaitAndAck() instead. 65 // 66 // Precondition: Only one goroutine may call any Wait* method at a time. 67 func (w *Waiter) Wait() Set { 68 return w.WaitFor(AllEvents) 69 } 70 71 // WaitFor blocks until at least one event in es is pending, then returns the 72 // set of pending events (including those not in es). It does not affect the 73 // set of pending events; callers must call w.Ack() to do so. 74 // 75 // Precondition: Only one goroutine may call any Wait* method at a time. 76 func (w *Waiter) WaitFor(es Set) Set { 77 for { 78 // Optimization: Skip the atomic store to w.g if an event is already 79 // pending. 80 if p := w.r.Pending(); p&es != NoEvents { 81 return p 82 } 83 84 // Indicate that we're preparing to go to sleep. 85 atomic.StoreUintptr(&w.g, preparingG) 86 87 // If an event is pending, abort the sleep. 88 if p := w.r.Pending(); p&es != NoEvents { 89 atomic.StoreUintptr(&w.g, 0) 90 return p 91 } 92 93 // If w.g is still preparingG (i.e. w.NotifyPending() has not been 94 // called or has not reached atomic.SwapUintptr()), go to sleep until 95 // w.NotifyPending() => goready(). 96 sync.Gopark(waiterCommit, unsafe.Pointer(&w.g), sync.WaitReasonSelect, sync.TraceEvGoBlockSelect, 0) 97 } 98 } 99 100 //go:norace 101 //go:nosplit 102 func waiterCommit(g uintptr, wg unsafe.Pointer) bool { 103 // The only way this CAS can fail is if a call to Waiter.NotifyPending() 104 // has replaced *wg with nil, in which case we should not sleep. 105 return sync.RaceUncheckedAtomicCompareAndSwapUintptr((*uintptr)(wg), preparingG, g) 106 } 107 108 // Ack marks the given events as not pending. 109 func (w *Waiter) Ack(es Set) { 110 w.r.Ack(es) 111 } 112 113 // WaitAndAckAll blocks until at least one event is pending, then marks all 114 // events as not pending and returns the set of previously-pending events. 115 // 116 // Precondition: Only one goroutine may call any Wait* method at a time. 117 func (w *Waiter) WaitAndAckAll() Set { 118 // Optimization: Skip the atomic store to w.g if an event is already 119 // pending. Call Pending() first since, in the common case that events are 120 // not yet pending, this skips an atomic swap on w.r.pending. 121 if w.r.Pending() != NoEvents { 122 if p := w.r.PendingAndAckAll(); p != NoEvents { 123 return p 124 } 125 } 126 127 for { 128 // Indicate that we're preparing to go to sleep. 129 atomic.StoreUintptr(&w.g, preparingG) 130 131 // If an event is pending, abort the sleep. 132 if w.r.Pending() != NoEvents { 133 if p := w.r.PendingAndAckAll(); p != NoEvents { 134 atomic.StoreUintptr(&w.g, 0) 135 return p 136 } 137 } 138 139 // If w.g is still preparingG (i.e. w.NotifyPending() has not been 140 // called or has not reached atomic.SwapUintptr()), go to sleep until 141 // w.NotifyPending() => goready(). 142 sync.Gopark(waiterCommit, unsafe.Pointer(&w.g), sync.WaitReasonSelect, sync.TraceEvGoBlockSelect, 0) 143 144 // Check for pending events. We call PendingAndAckAll() directly now since 145 // we only expect to be woken after events become pending. 146 if p := w.r.PendingAndAckAll(); p != NoEvents { 147 return p 148 } 149 } 150 } 151 152 // Notify marks the given events as pending, possibly unblocking concurrent 153 // calls to w.Wait() or w.WaitFor(). 154 func (w *Waiter) Notify(es Set) { 155 w.r.Notify(es) 156 } 157 158 // NotifyPending implements ReceiverCallback.NotifyPending. Users of Waiter 159 // should not call NotifyPending. 160 func (w *Waiter) NotifyPending() { 161 // Optimization: Skip the atomic swap on w.g if there is no sleeping 162 // goroutine. NotifyPending is called after w.r.Pending() is updated, so 163 // concurrent and future calls to w.Wait() will observe pending events and 164 // abort sleeping. 165 if atomic.LoadUintptr(&w.g) == 0 { 166 return 167 } 168 // Wake a sleeping G, or prevent a G that is preparing to sleep from doing 169 // so. Swap is needed here to ensure that only one call to NotifyPending 170 // calls goready. 171 if g := atomic.SwapUintptr(&w.g, 0); g > preparingG { 172 sync.Goready(g, 0, true /* wakep */) 173 } 174 } 175 176 var waiterPool = sync.Pool{ 177 New: func() any { 178 w := &Waiter{} 179 w.Init() 180 return w 181 }, 182 } 183 184 // GetWaiter returns an unused Waiter. PutWaiter should be called to release 185 // the Waiter once it is no longer needed. 186 // 187 // Where possible, users should prefer to associate each goroutine that calls 188 // Waiter.Wait() with a distinct pre-allocated Waiter to avoid allocation of 189 // Waiters in hot paths. 190 func GetWaiter() *Waiter { 191 return waiterPool.Get().(*Waiter) 192 } 193 194 // PutWaiter releases an unused Waiter previously returned by GetWaiter. 195 func PutWaiter(w *Waiter) { 196 waiterPool.Put(w) 197 }