github.com/haraldrudell/parl@v0.4.176/cyclic-awaitable.go (about) 1 /* 2 © 2023-present Harald Rudell <haraldrudell@proton.me> (https://haraldrudell.github.io/haraldrudell/) 3 All rights reserved 4 */ 5 6 package parl 7 8 import "sync/atomic" 9 10 // CyclicAwaitable is an awaitable that can be re-initialized 11 // - initialization-free, start in Open state 12 // - one-to-many, happens-before 13 // - the synchronization mechanic is closing channel, allowing consumers to await 14 // multiple events 15 // - [CyclicAwaitable.IsClosed] provides thread-safe observability 16 // - [CyclicAwaitable.Close] is idempotent, thread-safe and deferrable 17 // - Open means event is pending, Close means event has triggered 18 // - [CyclicAwaitable.Open] arms the awaitable returning a channel guaranteed to be 19 // open at timeof invocation 20 // - — 21 // - because Awaitable is renewed, access is via atomic Pointer 22 // - Pointer to struct allows for atomic update of IsClosed and Open 23 // 24 // Usage: 25 // 26 // valueWaiter *CyclicAwaitable 27 // … 28 // func (v *V) getOrWaitForValue(value T) { 29 // var hasValue bool 30 // // check if value is already present 31 // if value, hasValue = v.hasValueFromThread(); hasValue { 32 // return 33 // } 34 // // arm cyclable 35 // v.valueWaiter.Open() 36 // // collect any value arriving prior to arming cyclable 37 // if value, hasValue = v.hasValueFromThread(); hasValue { 38 // return 39 // } 40 // <- v.valueWaiter.Ch() 41 // value, _ = v.hasValueFromThread() 42 // … 43 // func (v *V) threadStoresValue(value T) { 44 // v.store(value) 45 // v.valueWaiter.Close() 46 type CyclicAwaitable struct{ awp atomic.Pointer[Awaitable] } 47 48 // Ch returns an awaitable channel. Thread-safe 49 func (a *CyclicAwaitable) Ch() (ch AwaitableCh) { return a.aw().Ch() } 50 51 // isClosed inspects whether the awaitable has been triggered 52 // - isClosed indicates that the channel is closed 53 // - Thread-safe 54 func (a *CyclicAwaitable) IsClosed() (isClosed bool) { return a.aw().IsClosed() } 55 56 // Close triggers awaitable by closing the channel 57 // - upon return, the channel is guaranteed to be closed 58 // - eventuallyConsistent [EvCon]: may return before the channel is atcually closed 59 // for higher performance 60 // - idempotent, deferrable, panic-free, thread-safe 61 func (a *CyclicAwaitable) Close(eventuallyConsistent ...bool) (didClose bool) { return a.aw().Close() } 62 63 // Open rearms the awaitable for another cycle 64 // - ch is guaranteed to have been open at time of invocation. 65 // Because each Open may return a different channel, 66 // use of the returned ch offers consistent state 67 // - didOpen is true if the channel was encountered closed 68 // - idempotent, thread-safe, panic-free 69 func (a *CyclicAwaitable) Open() (didOpen bool, ch AwaitableCh) { 70 71 // eventually consistency does not work for Open 72 // - Close involves competing threads operating on a unqiue Awaitable 73 // that has only one event 74 // - eventual consistency for Open would require synchronization 75 // of Close and Open invocations 76 // - this would cost 0.4955 ns for every Close and Open 77 // - potential savings is only 1.5 ns for detecting an Open following another Open 78 79 // openAwaitable as pointer defers allocation 80 var openAwaitable *Awaitable 81 for { 82 83 // inspect existing Awaitable 84 var awaitable = a.awp.Load() 85 if awaitable != nil && !awaitable.IsClosed() { 86 // at time of IsClosed, the channel was open 87 ch = awaitable.Ch() 88 return // was open return 89 } 90 91 // an Awaitable must be created. It was either: 92 // - uninitialized or 93 // - closed 94 95 // create Awaitable candidate 96 if openAwaitable == nil { 97 openAwaitable = &Awaitable{} 98 } 99 if didOpen = a.awp.CompareAndSwap(awaitable, openAwaitable); didOpen { 100 // at time of CAS, channel was open 101 ch = openAwaitable.Ch() 102 return // did open the channel return 103 } 104 } 105 } 106 107 // aw returns the active awaitable using atomic mechanic 108 func (a *CyclicAwaitable) aw() (aw *Awaitable) { 109 110 // read allocated Awaitable 111 if aw = a.awp.Load(); aw != nil { 112 return // existing awaitable return 113 } 114 115 // create authoritative Awaitable 116 aw = &Awaitable{} 117 if a.awp.CompareAndSwap(nil, aw) { 118 return // wrote new awaitable return 119 } 120 121 // return other thread’s Awaitable 122 return a.awp.Load() 123 }