github.com/haraldrudell/parl@v0.4.176/ptime/closing-ticker.go (about) 1 /* 2 © 2022–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package ptime 7 8 import ( 9 "sync" 10 "sync/atomic" 11 "time" 12 13 "github.com/haraldrudell/parl/internal/cyclebreaker" 14 "github.com/haraldrudell/parl/perrors" 15 ) 16 17 // ClosingTicker is like time.Ticker but the channel C closes on shutdown. 18 // A closing channel is detectable by listening threads. 19 // If the computer is busy swapping, ticks will be lost. 20 // MaxDuration indicates the longest time observed between ticks. 21 // MaxDuration is normally 1 s 22 type ClosingTicker struct { 23 C <-chan time.Time 24 MaxDuration time.Duration // atomic int64 25 perrors.ParlError // panic in Shutdown or panic in tick thread 26 isShutdownRequest chan struct{} 27 isTickThreadExit chan struct{} 28 shutdownOnce sync.Once 29 } 30 31 // NewClosingTicker returns a Ticker whose channel C closes on shutdown. 32 // A closing channel is detectable by a listening thread. 33 func NewClosingTicker(d time.Duration) (t *ClosingTicker) { 34 ch := make(chan time.Time) 35 ticker := time.NewTicker(d) 36 t0 := ClosingTicker{ 37 C: ch, 38 isShutdownRequest: make(chan struct{}), 39 isTickThreadExit: make(chan struct{}), 40 } 41 42 // luanch our tick thread 43 go t0.tick(ch, ticker) 44 45 return &t0 46 } 47 48 // Shutdown causes the channel C to close and resources to be released 49 func (t *ClosingTicker) Shutdown() { 50 t.shutdownOnce.Do(func() { 51 defer cyclebreaker.Recover2(func() cyclebreaker.DA { return cyclebreaker.A() }, nil, t.AddErrorProc) 52 53 close(t.isShutdownRequest) 54 <-t.isTickThreadExit 55 }) 56 } 57 58 func (t *ClosingTicker) GetError() (maxDuration time.Duration, err error) { 59 maxDuration = time.Duration(atomic.LoadInt64((*int64)(&t.MaxDuration))) 60 err = t.ParlError.GetError() 61 return 62 } 63 64 func (t *ClosingTicker) tick(out chan time.Time, ticker *time.Ticker) { 65 defer close(t.isTickThreadExit) 66 defer cyclebreaker.Recover2(func() cyclebreaker.DA { return cyclebreaker.A() }, nil, t.AddErrorProc) 67 defer close(out) 68 defer ticker.Stop() 69 70 var last time.Time 71 var maxDuration time.Duration 72 73 C := ticker.C 74 isShutdown := t.isShutdownRequest 75 for { 76 select { 77 case timeValue := <-C: 78 out <- timeValue 79 if !last.IsZero() { 80 d := timeValue.Sub(last) 81 if d > maxDuration { 82 maxDuration = d 83 atomic.StoreInt64((*int64)(&t.MaxDuration), int64(maxDuration)) 84 } 85 } 86 last = timeValue 87 continue 88 case <-isShutdown: 89 } 90 break 91 } 92 }