github.com/haraldrudell/parl@v0.4.176/once.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package parl 7 8 import ( 9 "sync" 10 "sync/atomic" 11 ) 12 13 // parl.Once is an observable sync.Once with an alternative DoErr method 14 // - [Once.DoErr] invokes a function returning error recovering a panic 15 // - [Once.IsDone] returns whether the Once has been executed, atomic performance 16 // - [Once.Result] returns a possible [Once.DoErr] outcome, atomic performance 17 // - [Once.Do] is similar to [sync.Once.Do] 18 // - parl.Once is thread-safe and does not require initialization 19 // - No thread will return from [Once.Do] or [Once.DoErr] until once.Do or once.DoErr has completed 20 type Once struct { 21 // sync.Once is not observable 22 once sync.Once 23 // isDone indicates if the Once has completed, either by Do or DoErr 24 // - provides observability 25 isDone atomic.Bool 26 // result is the outcome of a possible DoErr invocation 27 // - if nil, either the Once has not triggered or 28 // it was triggered by Once.Do that does not have a result 29 result atomic.Pointer[onceDoErrResult] 30 } 31 32 // onceDoErrResult contains the result of a DoErr invocation 33 type onceDoErrResult struct { 34 isPanic bool 35 err error 36 } 37 38 // DoErr calls the function if and only if Do or DoErr is being called for the first time 39 // for this instance of Once 40 // - didOnce is true if this invocation first and actually invoked doErrFuncArgument 41 // - isPanic is true if this or previous invocation did panic 42 // - err is either the return value or the panic value from this or previous invocation 43 // - thread-safe 44 // - — 45 // - because sync.Once.Do has fixed signature, 46 // Do must be invoke a function wrapper 47 // - once.Do must execute for happens before guarantee 48 // 49 // Usage: 50 // 51 // var once parl.Once 52 // var didTheClose, isPanic, err = once.DoErr(osFile.Close) 53 func (o *Once) DoErr(doErrFuncArgument func() (err error)) (didOnce, isPanic bool, err error) { 54 55 // wrapper provides the wrapper function for sync.Once.Do 56 // - once.Do must be invoked every time for happens-before 57 // - therefore, wrapper must always be present 58 var wrapper = onceDoErr{ 59 doErrFuncArgument: doErrFuncArgument, 60 didOnce: &didOnce, 61 isPanic: &isPanic, 62 errp: &err, 63 Once: o, 64 } 65 66 // execute once.Do to obtain happens-before guarantee 67 o.once.Do(wrapper.invokeDoErrFuncArgument) 68 69 if didOnce { 70 return // updated by this invocation, isPanic and err are valid return 71 } 72 73 // o.once.Do was already triggered and did nothing 74 // - fetch possible previous result 75 isPanic, _, err = o.Result() 76 77 return 78 } 79 80 // Do calls the function if and only if Do or DoErr is being called for the first time 81 // for this instance of Once. Thread-safe 82 // - a panic is not recovered 83 // - thread-safe 84 // - — 85 // - once.Do must execute for happens before guarantee 86 // 87 // Usage: 88 // 89 // var once parl.Once 90 // once.Do(myFunc) 91 // … 92 // if once.IsDone() … 93 // func myFunc() { … 94 func (o *Once) Do(doFuncArgument func()) { 95 96 // because isDone must be set inside of once.Do, 97 // the doFuncArgument argument must be invoked inside a wrapper function 98 // - the wrapper function must have access to doFuncArgument 99 // - because once.Do must be invoked every time, 100 // the wrapper must alway be present 101 var d = onceDo{doFuncArgument: doFuncArgument, Once: o} 102 103 o.once.Do(d.invokeF) 104 } 105 106 // IsDone returns true if Once did execute 107 // - thread-safe, atomic performance 108 func (o *Once) IsDone() (isDone bool) { return o.isDone.Load() } 109 110 // Result returns the [Once.DoErr] outcome provided with atomic performance 111 // - values are only valid if hasResult is true 112 // - hasResult is false when: 113 // - — the Once has not triggered or 114 // - — the Once was triggered by [Once.Do] 115 // - thread-safe 116 func (o *Once) Result() (isPanic bool, hasResult bool, err error) { 117 var result = o.result.Load() 118 if hasResult = result != nil; !hasResult { 119 return // no result available return 120 } 121 122 isPanic = result.isPanic 123 err = result.err 124 125 return 126 }