-
Notifications
You must be signed in to change notification settings - Fork 0
/
wait.go
137 lines (116 loc) · 2.85 KB
/
wait.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
package wait
import (
"context"
"errors"
"math/rand"
"time"
)
func init() {
rand.Seed(time.Now().UnixNano())
}
var (
canceledErr = errors.New("the operation was canceled")
invalidBackoffLimitErr = errors.New("the provided backoff limit is lower then the baseline")
)
type UntilOptions struct {
Interval time.Duration
Jitter int
}
func (o *UntilOptions) jitterDefined() bool {
return o.Jitter != -1 && o.Jitter != 0
}
var defaultUntilOptions = &UntilOptions{
Interval: time.Millisecond * 100,
Jitter: 0,
}
// Until allows for a predictable interval based waiting mechanism until
// the given bool based check is satisfied.
func Until(ctx context.Context, check func() (bool, error), o ...*UntilOptions) error {
options := defaultUntilOptions
if len(o) != 0 {
options = o[0]
}
calculateNextInterval := func() time.Duration {
if !options.jitterDefined() {
return options.Interval
}
return jitterDuration(options.Interval, options.Jitter)
}
t := time.NewTimer(calculateNextInterval())
for {
select {
case <-t.C:
res, err := check()
if err != nil {
return err
}
if !res {
t.Reset(calculateNextInterval())
continue
}
return nil
case <-ctx.Done():
return canceledErr
}
}
}
type BackoffOptions struct {
Jitter int
BaselineDuration, Limit time.Duration
Multiplier int64
}
func (o * BackoffOptions) jitterDefined() bool {
return o.Jitter != -1 && o.Jitter != 0
}
var defaultBackoffOptions = &BackoffOptions{
BaselineDuration: time.Millisecond,
Limit: 500 * time.Millisecond,
Multiplier: 2,
Jitter: 0,
}
// Backoff is a waiting mechanism that allows for better CPU load as the interval
// starts from a given baseline and then backs off until it reaches the provided
// limit.
//
// Note: this is partially bases off of http.Server implementation of their
// Shutdown polling mechanism.
func Backoff(ctx context.Context, check func() (bool, error), o ...*BackoffOptions) error {
options := defaultBackoffOptions
if len(o) != 0 {
options = o[0]
}
// make sure limit is greater then the given duration
if options.Limit < options.BaselineDuration {
return invalidBackoffLimitErr
}
duration := options.BaselineDuration
t := time.NewTimer(duration)
calcNewDuration := func(previous time.Duration) time.Duration {
d := time.Duration(int64(previous) * int64(options.Multiplier))
if !options.jitterDefined() {
return d
}
return jitterDuration(d, options.Jitter)
}
for {
select {
case <-ctx.Done():
return canceledErr
case <-t.C:
res, err := check()
if err != nil {
return err
}
if res {
return nil
}
if duration < options.Limit {
duration = calcNewDuration(duration)
} else {
// we cap the timer duration to the limit
duration = options.Limit
}
t.Reset(duration)
}
}
}