github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/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/metacubex/gvisor/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.TraceBlockSelect, 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.TraceBlockSelect, 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  }