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 }