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  }