github.com/haraldrudell/parl@v0.4.176/internal/cyclebreaker/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 cyclebreaker
     7  
     8  import "sync/atomic"
     9  
    10  // Awaitable is a semaphore allowing any number of threads to observe
    11  // and await an event
    12  //   - one-to-many, happens-before
    13  //   - the synchronization mechanic is closing channel, allowing consumers to await
    14  //     multiple events
    15  //   - IsClosed provides thread-safe observability
    16  //   - Close is idempotent, thread-safe, deferrable and panic-free
    17  //   - [parl.CyclicAwaitable] is re-armable, cyclic version
    18  //   - —
    19  //   - alternative low-blocking inter-thread mechanics are [sync.WaitGroup] and [sync.RWMutex]
    20  //     but those are less performant for the managing thread
    21  type Awaitable struct {
    22  	isClosed atomic.Bool
    23  	ch       chan struct{}
    24  }
    25  
    26  // NewAwaitable returns a one-to-many sempahore
    27  func NewAwaitable() (awaitable *Awaitable) {
    28  	return &Awaitable{ch: make(chan struct{})}
    29  }
    30  
    31  // Ch returns an awaitable channel. Thread-safe
    32  func (a *Awaitable) Ch() (ch AwaitableCh) {
    33  	return a.ch
    34  }
    35  
    36  // isClosed inspects whether the awaitable has been triggered
    37  //   - Thread-safe
    38  func (a *Awaitable) IsClosed() (isClosed bool) {
    39  	select {
    40  	case <-a.ch:
    41  		isClosed = true
    42  	default:
    43  	}
    44  	return
    45  }
    46  
    47  // Close triggers awaitable by closing the channel
    48  //   - upon return, the channel is guaranteed to be closed
    49  //   - idempotent, deferrable, panic-free, thread-safe
    50  func (a *Awaitable) Close() (didClose bool) {
    51  	if didClose = a.isClosed.CompareAndSwap(false, true); !didClose {
    52  		<-a.ch // prevent returning before channel close
    53  		return // already closed return
    54  	}
    55  	close(a.ch)
    56  	return // didClose return
    57  }