github.com/haraldrudell/parl@v0.4.176/once-ch.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  const (
    11  	// [OnceCh.IsWinner] loser threads do not wait
    12  	NoOnceWait = true
    13  	// [OnceCh.IsWinner] loser threads wait
    14  	LoserWait = false
    15  )
    16  
    17  // OnceCh implements a one-time execution filter
    18  //   - initialization free
    19  //   - OnceCh is similar to [sync.Once] with improvements:
    20  //   - — does not require an awkward function value to be provided.
    21  //     Method dunction-values cause allocation
    22  //   - — awaitable channel mechanic means threads can
    23  //     await multiple events
    24  //   - — is observable via [OnceCh.IsInvoked] [OnceCh.IsClosed]
    25  //
    26  // Usage:
    27  //
    28  //	var o OnceCh
    29  //	if isWinner, done := o.IsWinner(); !isWinner {
    30  //	  return // thread already waited for winner thread completion
    31  //	} else {
    32  //	  defer done.Done()
    33  //	}
    34  //	…
    35  type OnceCh struct {
    36  	// winner selects the winning thread
    37  	winner atomic.Bool
    38  	// done allows:
    39  	//	- winner thread to indicate completion
    40  	//	- loser threads to await winner completion
    41  	done doneOnce
    42  }
    43  
    44  type doneOnce struct {
    45  	// awaitable is mechanic for loser threads to
    46  	// await winner execution complete
    47  	awaitable Awaitable
    48  }
    49  
    50  var _ Done = &doneOnce{}
    51  
    52  func (d *doneOnce) Done() {
    53  	d.awaitable.Close()
    54  }
    55  
    56  // IsWinner selects winner thread as the first of invokers
    57  //   - noWait missing or LoserWait: loser thread wait for winner thread invoking done.Done
    58  //   - noWait NoOnceWait: eventually consistent: loser threads immediately return
    59  //   - isWinner true: this is the winner first invocation.
    60  //   - — must invoke done.Done upon task completion
    61  //   - isWinner false: loser thread, done is nil.
    62  //     May have already awaited winner thread completion
    63  func (o *OnceCh) IsWinner(noWait ...bool) (isWinner bool, done Done) {
    64  
    65  	// pick winner thread
    66  	if isWinner = o.winner.CompareAndSwap(false, true); isWinner {
    67  		done = &o.done
    68  		return // winner return
    69  	}
    70  
    71  	// loser threads wait for winner thread unless noWait: NoOnceWait
    72  	if len(noWait) == 0 || !noWait[0] {
    73  		<-o.done.awaitable.Ch()
    74  	}
    75  
    76  	return
    77  }
    78  
    79  // Ch returns a channel that closes once IsWinner and done have both been invoked
    80  func (o *OnceCh) Ch() (ch AwaitableCh) { return o.done.awaitable.Ch() }
    81  
    82  // IsInvoked indicates that a winner was selected
    83  func (o *OnceCh) IsInvoked() (isInvoked bool) { return o.winner.Load() }
    84  
    85  // IsClosed indicates that a winner was selected and invoked done
    86  func (o *OnceCh) IsClosed() (isClosed bool) { return o.done.awaitable.IsClosed() }