github.com/ttpreport/gvisor-ligolo@v0.0.0-20240123134145-a858404967ba/pkg/syncevent/receiver.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  	"github.com/ttpreport/gvisor-ligolo/pkg/atomicbitops"
    19  )
    20  
    21  // Receiver is an event sink that holds pending events and invokes a callback
    22  // whenever new events become pending. Receiver's methods may be called
    23  // concurrently from multiple goroutines.
    24  //
    25  // Receiver.Init() must be called before first use.
    26  type Receiver struct {
    27  	// pending is the set of pending events. pending is accessed using atomic
    28  	// memory operations.
    29  	pending atomicbitops.Uint64
    30  
    31  	// cb is notified when new events become pending. cb is immutable after
    32  	// Init().
    33  	cb ReceiverCallback
    34  }
    35  
    36  // ReceiverCallback receives callbacks from a Receiver.
    37  type ReceiverCallback interface {
    38  	// NotifyPending is called when the corresponding Receiver has new pending
    39  	// events.
    40  	//
    41  	// NotifyPending is called synchronously from Receiver.Notify(), so
    42  	// implementations must not take locks that may be held by callers of
    43  	// Receiver.Notify(). NotifyPending may be called concurrently from
    44  	// multiple goroutines.
    45  	NotifyPending()
    46  }
    47  
    48  // Init must be called before first use of r.
    49  func (r *Receiver) Init(cb ReceiverCallback) {
    50  	r.cb = cb
    51  }
    52  
    53  // Pending returns the set of pending events.
    54  func (r *Receiver) Pending() Set {
    55  	return Set(r.pending.Load())
    56  }
    57  
    58  // Notify sets the given events as pending.
    59  func (r *Receiver) Notify(es Set) {
    60  	p := Set(r.pending.Load())
    61  	// Optimization: Skip the atomic CAS on r.pending if all events are
    62  	// already pending.
    63  	if p&es == es {
    64  		return
    65  	}
    66  	// When this is uncontended (the common case), CAS is faster than
    67  	// atomic-OR because the former is inlined and the latter (which we
    68  	// implement in assembly ourselves) is not.
    69  	if !r.pending.CompareAndSwap(uint64(p), uint64(p|es)) {
    70  		// If the CAS fails, fall back to atomic-OR.
    71  		atomicbitops.OrUint64(&r.pending, uint64(es))
    72  	}
    73  	r.cb.NotifyPending()
    74  }
    75  
    76  // Ack unsets the given events as pending.
    77  func (r *Receiver) Ack(es Set) {
    78  	p := Set(r.pending.Load())
    79  	// Optimization: Skip the atomic CAS on r.pending if all events are
    80  	// already not pending.
    81  	if p&es == 0 {
    82  		return
    83  	}
    84  	// When this is uncontended (the common case), CAS is faster than
    85  	// atomic-AND because the former is inlined and the latter (which we
    86  	// implement in assembly ourselves) is not.
    87  	if !r.pending.CompareAndSwap(uint64(p), uint64(p&^es)) {
    88  		// If the CAS fails, fall back to atomic-AND.
    89  		atomicbitops.AndUint64(&r.pending, ^uint64(es))
    90  	}
    91  }
    92  
    93  // PendingAndAckAll unsets all events as pending and returns the set of
    94  // previously-pending events.
    95  //
    96  // PendingAndAckAll should only be used in preference to a call to Pending
    97  // followed by a conditional call to Ack when the caller expects events to be
    98  // pending (e.g. after a call to ReceiverCallback.NotifyPending()).
    99  func (r *Receiver) PendingAndAckAll() Set {
   100  	return Set(r.pending.Swap(0))
   101  }