github.com/alwitt/goutils@v0.6.4/timer.go (about) 1 package goutils 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "github.com/apex/log" 10 ) 11 12 // TimeoutHandler callback function signature called timer timeout 13 type TimeoutHandler func() error 14 15 // IntervalTimer is a support interface for triggering events at specific intervals 16 type IntervalTimer interface { 17 /* 18 Start starts timer with a specific timeout interval, and the callback to trigger on timeout. 19 20 If oneShort, cancel after first timeout. 21 22 @param interval time.Duration - timeout interval 23 @param handler TimeoutHandler - handler to trigger on timeout 24 @param oneShort bool - if true, timer stop after first activation 25 */ 26 Start(interval time.Duration, handler TimeoutHandler, oneShort bool) error 27 28 /* 29 Stop stops the timer 30 */ 31 Stop() error 32 } 33 34 // intervalTimerImpl implements IntervalTimer 35 type intervalTimerImpl struct { 36 Component 37 running bool 38 rootContext context.Context 39 operationContext context.Context 40 contextCancel context.CancelFunc 41 wg *sync.WaitGroup 42 } 43 44 /* 45 GetIntervalTimerInstance get an implementation instance of IntervalTimer 46 47 @param rootCtxt context.Context - the base Context the timer will derive new runtime context from 48 each time Start is called. 49 @param wg *sync.WaitGroup - WaitGroup use by timer 50 @param logTags log.Fields - log metadata fields 51 @return an IntervalTimer instance 52 */ 53 func GetIntervalTimerInstance( 54 rootCtxt context.Context, wg *sync.WaitGroup, logTags log.Fields, 55 ) (IntervalTimer, error) { 56 return &intervalTimerImpl{ 57 Component: Component{LogTags: logTags}, 58 running: false, 59 rootContext: rootCtxt, 60 operationContext: nil, 61 contextCancel: nil, 62 wg: wg, 63 }, nil 64 } 65 66 /* 67 Start starts timer with a specific timeout interval, and the callback to trigger on timeout. 68 69 If oneShort, cancel after first timeout. 70 71 @param interval time.Duration - timeout interval 72 @param handler TimeoutHandler - handler to trigger on timeout 73 @param oneShort bool - if true, timer stop after first activation 74 */ 75 func (t *intervalTimerImpl) Start( 76 interval time.Duration, handler TimeoutHandler, oneShot bool, 77 ) error { 78 if t.running { 79 return fmt.Errorf("already running") 80 } 81 log.WithFields(t.LogTags).Infof("Starting with int %s", interval) 82 t.wg.Add(1) 83 t.running = true 84 ctxt, cancel := context.WithCancel(t.rootContext) 85 t.operationContext = ctxt 86 t.contextCancel = cancel 87 go func() { 88 defer t.wg.Done() 89 defer log.WithFields(t.LogTags).Info("Timer loop exiting") 90 defer func() { 91 t.running = false 92 }() 93 finished := false 94 for !finished { 95 select { 96 case <-t.operationContext.Done(): 97 finished = true 98 case <-time.After(interval): 99 log.WithFields(t.LogTags).Debug("Calling handler") 100 if err := handler(); err != nil { 101 log.WithError(err).WithFields(t.LogTags).Error("Handler failed") 102 } 103 if oneShot { 104 return 105 } 106 } 107 } 108 }() 109 return nil 110 } 111 112 /* 113 Stop stops the timer 114 */ 115 func (t *intervalTimerImpl) Stop() error { 116 if t.contextCancel != nil { 117 log.WithFields(t.LogTags).Info("Stopping timer loop") 118 t.contextCancel() 119 } 120 return nil 121 } 122 123 // ======================================================================================== 124 125 // Sequencer is a helper interface for returning a sequence of numbers 126 type Sequencer interface { 127 /* 128 NextValue returns the next value in the sequence 129 */ 130 NextValue() float64 131 } 132 133 // exponentialSequence is a helper interface to get an exponential sequence from a 134 // starting value 135 type exponentialSequence struct { 136 current float64 137 growthRate float64 138 } 139 140 /* 141 NextValue returns the next value in the sequence 142 */ 143 func (s *exponentialSequence) NextValue() float64 { 144 nextValue := s.current * s.growthRate 145 s.current = nextValue 146 return nextValue 147 } 148 149 /* 150 GetExponentialSeq define an exponential sequencer 151 152 @param initial float64 - initial value 153 @param growthRate float64 - EXP change rate 154 @return an Sequencer instance 155 */ 156 func GetExponentialSeq(initial float64, growthRate float64) (Sequencer, error) { 157 if growthRate < 1.0 { 158 return nil, fmt.Errorf("growth rate of exponential sequence must be > 1.0") 159 } 160 return &exponentialSequence{current: initial, growthRate: growthRate}, nil 161 }