github.com/haraldrudell/parl@v0.4.176/ptime/invoke-on-timer.go (about) 1 /* 2 © 2023–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package ptime 7 8 import ( 9 "time" 10 11 "github.com/haraldrudell/parl/internal/cyclebreaker" 12 "github.com/haraldrudell/parl/perrors" 13 ) 14 15 const ( 16 defaultInvokeOnTimerDuration = 100 * time.Millisecond 17 ) 18 19 // InvokeOnTimer implements a timeout action 20 type InvokeOnTimer struct { 21 awaitable *cyclebreaker.Awaitable 22 } 23 24 // NewInvokeOnTimer panics or invokes elapsedFunc if not reset within d 25 // - default d 100 ms 26 // - on timer expiry: 27 // - — if errPanic is non-nil, process-terminating panic 28 // - — if elapsedFunc is non-nil, it is invoked. 29 // Panics in elapsedFunc are recovered 30 // - errPanic and elapsedFunc cannot both be nil 31 // - each NewInvokeOnTimer launches a separate, blocked thread 32 // - if errCh is present it should be a buffered channel receving 33 // a value on thread-exit that may be a panic from the thread. 34 // the thread should not have any panics but a panic may occur in 35 // elapsedFunc. 36 // - if errCh is missing or nil, panic is echoed to stderr 37 // - resources are released on: 38 // - — Stop invocation 39 // - — timer firing 40 func NewInvokeOnTimer(d time.Duration, errPanic error, elapsedFunc func(), errCh ...chan<- error) (timer *InvokeOnTimer) { 41 if errPanic == nil && elapsedFunc == nil { 42 cyclebreaker.NilError("errPanic and elapsedFunc") 43 } 44 if d <= 0 { 45 d = defaultInvokeOnTimerDuration 46 } 47 var errCh0 chan<- error 48 if len(errCh) > 0 { 49 errCh0 = errCh[0] 50 } 51 52 var t = InvokeOnTimer{awaitable: cyclebreaker.NewAwaitable()} 53 go invokeOnTimerThread( 54 time.NewTimer(d), t.awaitable.Ch(), 55 errPanic, elapsedFunc, 56 errCh0, 57 ) 58 return &t 59 } 60 61 // Stop cancels the timer and releases resources 62 func (t *InvokeOnTimer) Stop() { t.awaitable.Close() } 63 64 // the thread handling timer expiry 65 func invokeOnTimerThread( 66 timer *time.Timer, c cyclebreaker.AwaitableCh, 67 errPanic error, elapsedFunc func(), 68 errCh chan<- error, 69 ) { 70 var err error 71 if errCh != nil { 72 defer cyclebreaker.SendErr(errCh, &err) 73 } else { 74 defer infallible(&err) 75 } 76 // errPanic non-nil causes process termination by panic outside of RecoverErr 77 if errPanic != nil { 78 defer processTermination(&errPanic) 79 } 80 defer cyclebreaker.RecoverErr(func() cyclebreaker.DA { return cyclebreaker.A() }, &err) 81 defer timer.Stop() 82 83 select { 84 case <-timer.C: 85 if errPanic != nil { 86 return // terminate the process 87 } 88 elapsedFunc() // panic will be recovered 89 case <-c: 90 errPanic = nil 91 } 92 } 93 94 func infallible(errp *error) { 95 var err = *errp 96 if err == nil { 97 return 98 } 99 cyclebreaker.Infallible(err) 100 } 101 102 func processTermination(errp *error) { 103 var err = *errp 104 if err == nil { 105 return 106 } 107 // if the err has stack, ensure it is printed 108 if perrors.HasStack(err) { 109 cyclebreaker.Log(perrors.Long(err)) 110 } 111 panic(err) 112 }