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 }