-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstrategies.go
112 lines (95 loc) · 3.25 KB
/
strategies.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
package retry
import (
"errors"
"math/rand"
"time"
)
var ErrDelaysSpent = errors.New("all delays spent")
var ErrStopped = errors.New("stopped")
// StopDelay indicates that no more retries should be made.
const StopDelay time.Duration = -1
// Iterator is a delays generator.
type Iterator func() (time.Duration, error)
// Strategy is a retrying strategy for retrying an operation.
type Strategy interface {
Iterator() Iterator
}
// Delays is a retry strategy that returns specified delays.
type Delays []time.Duration
// Iterator returns the specified delays generator.
func (d Delays) Iterator() Iterator {
i := 0
return func() (time.Duration, error) {
if i >= len(d) {
return StopDelay, ErrDelaysSpent
}
current := d[i]
i++
return current, nil
}
}
// Constant is a retry strategy that always returns the same retry delay.
type Constant time.Duration
// Iterator returns constant delay generator.
func (c Constant) Iterator() Iterator {
return func() (time.Duration, error) {
if time.Duration(c) == StopDelay {
return time.Duration(c), ErrStopped
}
return time.Duration(c), nil
}
}
// Zero is zero delayed strategy is a fixed retry strategy whose retry time is always zero,
// meaning that the operation is retried immediately without waiting, indefinitely.
func Zero() Constant {
return Constant(0)
}
// Stop is a fixed retry strategy that always returns StopDelay,
// meaning that the operation should never be retried.
func Stop() Constant {
return Constant(StopDelay)
}
// ExponentialBackOff is exponential backoff strategy.
type ExponentialBackOff struct {
// Start delay.
Start time.Duration
// Multiplier factor. Next delay = delay * multiplier.
Factor float64
// Delay randomization. delay = delay * (random value in range [1 - Jitter, 1 + Jitter]).
Jitter float64
// Delay maximum.
MaxDelay time.Duration
}
// Exponential creates exponential backoff strategy.
func Exponential(start time.Duration, factor float64, jitter float64) ExponentialBackOff {
return ExponentialBackOff{Start: start, Factor: factor, Jitter: jitter}
}
// TruncatedExponential creates exponential backoff strategy with max delay.
func TruncatedExponential(start time.Duration, factor, jitter float64, maxDelay time.Duration) ExponentialBackOff {
return ExponentialBackOff{Start: start, Factor: factor, Jitter: jitter, MaxDelay: maxDelay}
}
// Iterator returns exponential backoff delays generator.
func (e ExponentialBackOff) Iterator() Iterator {
rand := rand.New(rand.NewSource(time.Now().UnixNano())) //nolint:gosec
delay := e.Start
return func() (time.Duration, error) {
cur := delay
delay = time.Duration(float64(delay) * e.Factor)
if e.MaxDelay != 0 && delay >= e.MaxDelay {
delay = e.MaxDelay
}
return jitter(cur, e.Jitter, rand.Float64()), nil
}
}
func jitter(delay time.Duration, factor, random float64) time.Duration {
if factor == 0 {
return delay
}
delta := factor * float64(delay)
minDelay := float64(delay) - delta
maxDelay := float64(delay) + delta
// Get a random value from the range [minInterval, maxInterval].
// The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
// we want a 33% chance for selecting either 1, 2 or 3.
return time.Duration(minDelay + (random * (maxDelay - minDelay + 1)))
}