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