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