github.com/haraldrudell/parl@v0.4.176/period-waiter.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/atomic" 10 ) 11 12 // PeriodWaiter temporarily holds threads invoking Wait 13 // - HoldWaiters causes any Wait invokers to block until ReleaseWaiters 14 // 15 // that has not been succeeded by a ReleaseWaiters invocation. Thread-safe 16 type PeriodWaiter struct { 17 // gen is initialized by HoldWaiters and set to nil by 18 // ReleaseWaiters 19 gen atomic.Pointer[periodWaiter] 20 } 21 22 type periodWaiter struct { 23 // waitGroup holds Wait invokers 24 wg WaitGroupCh 25 // number of threads in Wait 26 count atomic.Uint64 27 } 28 29 // NewPeriodWaiter returns an object that can hold threads temporarily. Thread-safe 30 // - initially [PeriodWaiter.Wait] invokers are not held 31 // - After invoking [PeriodWaiter.], Wait invokers are held until an invocation of 32 // [PeriodWaiter.ReleaseWaiters] 33 func NewPeriodWaiter() (periodWaiter *PeriodWaiter) { return &PeriodWaiter{} } 34 35 // HoldWaiters causes a thread invoking [PeriodWaiter.Wait] to wait. Thread-safe 36 // - idempotent 37 func (p *PeriodWaiter) HoldWaiters() { 38 39 // check for already waiting 40 if p.gen.Load() != nil { 41 return // already waiting return: noop 42 } 43 44 // try to add a created wait group 45 var pw periodWaiter 46 // add one causing [WaitGroupCh.Wait] to block 47 pw.wg.Add(1) 48 // atomic nil to value of p.wg 49 p.gen.CompareAndSwap(nil, &pw) 50 } 51 52 // ReleaseWaiters releases any threads blocked in [PeriodWaiter.Wait] 53 // and lets new Wait invokers proceed. Thread-safe 54 func (p *PeriodWaiter) ReleaseWaiters() { 55 56 // atomic read of the current wait group 57 var pw = p.gen.Load() 58 if pw == nil { 59 return // state not waiting return: noop 60 } 61 62 // resolve any successfully obtained waitgroup 63 if !p.gen.CompareAndSwap(pw, nil) { 64 return // other thread already niled it: noop 65 } 66 pw.wg.Done() 67 } 68 69 // Count returns the number of threads currently in Wait 70 // - threads invoking [PeriodWaiter.Ch] or 71 func (p *PeriodWaiter) Count() (waitingThreads int) { 72 if pw := p.gen.Load(); pw != nil { 73 waitingThreads = int(pw.count.Load()) 74 } 75 return 76 } 77 78 // IsHold returns true if Wait will currently block 79 func (p *PeriodWaiter) IsHold() (isHold bool) { return p.gen.Load() != nil } 80 81 // Ch returns a channel that closes on ReleaseWaiters 82 // - ch is nil if not currently waiting 83 func (p *PeriodWaiter) Ch() (ch AwaitableCh) { 84 if pw := p.gen.Load(); pw != nil { 85 ch = pw.wg.Ch() 86 } 87 return 88 } 89 90 // Wait blocks the thread if a HoldWaiters invocation took place with no 91 // ReleaseWaiters succeeding it. Thread-safe 92 func (p *PeriodWaiter) Wait() { 93 94 // keep waiting until the current wg value is nil 95 for { 96 var pw = p.gen.Load() 97 if pw == nil { 98 return // no mandated wait: noop return 99 } 100 pw.count.Add(1) 101 pw.wg.Wait() 102 } 103 }