-
Notifications
You must be signed in to change notification settings - Fork 30
/
backoff.go
134 lines (111 loc) · 3.16 KB
/
backoff.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
package retry
import (
"sync"
"time"
)
// Backoff is an interface that backs off.
type Backoff interface {
// Next returns the time duration to wait and whether to stop.
Next() (next time.Duration, stop bool)
}
var _ Backoff = (BackoffFunc)(nil)
// BackoffFunc is a backoff expressed as a function.
type BackoffFunc func() (time.Duration, bool)
// Next implements Backoff.
func (b BackoffFunc) Next() (time.Duration, bool) {
return b()
}
// WithJitter wraps a backoff function and adds the specified jitter. j can be
// interpreted as "+/- j". For example, if j were 5 seconds and the backoff
// returned 20s, the value could be between 15 and 25 seconds. The value can
// never be less than 0.
func WithJitter(j time.Duration, next Backoff) Backoff {
r := newLockedRandom(time.Now().UnixNano())
return BackoffFunc(func() (time.Duration, bool) {
val, stop := next.Next()
if stop {
return 0, true
}
diff := time.Duration(r.Int63n(int64(j)*2) - int64(j))
val = val + diff
if val < 0 {
val = 0
}
return val, false
})
}
// WithJitterPercent wraps a backoff function and adds the specified jitter
// percentage. j can be interpreted as "+/- j%". For example, if j were 5 and
// the backoff returned 20s, the value could be between 19 and 21 seconds. The
// value can never be less than 0 or greater than 100.
func WithJitterPercent(j uint64, next Backoff) Backoff {
r := newLockedRandom(time.Now().UnixNano())
return BackoffFunc(func() (time.Duration, bool) {
val, stop := next.Next()
if stop {
return 0, true
}
// Get a value between -j and j, the convert to a percentage
top := r.Int63n(int64(j)*2) - int64(j)
pct := 1 - float64(top)/100.0
val = time.Duration(float64(val) * pct)
if val < 0 {
val = 0
}
return val, false
})
}
// WithMaxRetries executes the backoff function up until the maximum attempts.
func WithMaxRetries(max uint64, next Backoff) Backoff {
var l sync.Mutex
var attempt uint64
return BackoffFunc(func() (time.Duration, bool) {
l.Lock()
defer l.Unlock()
if attempt >= max {
return 0, true
}
attempt++
val, stop := next.Next()
if stop {
return 0, true
}
return val, false
})
}
// WithCappedDuration sets a maximum on the duration returned from the next
// backoff. This is NOT a total backoff time, but rather a cap on the maximum
// value a backoff can return. Without another middleware, the backoff will
// continue infinitely.
func WithCappedDuration(cap time.Duration, next Backoff) Backoff {
return BackoffFunc(func() (time.Duration, bool) {
val, stop := next.Next()
if stop {
return 0, true
}
if val <= 0 || val > cap {
val = cap
}
return val, false
})
}
// WithMaxDuration sets a maximum on the total amount of time a backoff should
// execute. It's best-effort, and should not be used to guarantee an exact
// amount of time.
func WithMaxDuration(timeout time.Duration, next Backoff) Backoff {
start := time.Now()
return BackoffFunc(func() (time.Duration, bool) {
diff := timeout - time.Since(start)
if diff <= 0 {
return 0, true
}
val, stop := next.Next()
if stop {
return 0, true
}
if val <= 0 || val > diff {
val = diff
}
return val, false
})
}