-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathstrategy.go
122 lines (102 loc) · 3.49 KB
/
strategy.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
package retry
import (
"errors"
"math"
"math/rand"
"time"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/code-payments/code-server/pkg/retry/backoff"
)
// Strategy is a function that determines whether or not an action should be
// retried. Strategies are allowed to delay or cause other side effects.
type Strategy func(attempts uint, err error) bool
// Limit returns a strategy that limits the total number of retries.
// maxAttempts should be >= 1, since the action is evaluateed first.
func Limit(maxAttempts uint) Strategy {
return func(attempts uint, err error) bool {
return attempts < maxAttempts
}
}
// RetriableErrors returns a strategy that specifies which errors can be retried.
func RetriableErrors(retriableErrors ...error) Strategy {
return func(attempts uint, err error) bool {
for _, e := range retriableErrors {
if errors.Is(err, e) {
return true
}
}
return false
}
}
// NonRetriableErrors returns a strategy that specifies which errors should not be retried.
func NonRetriableErrors(nonRetriableErrors ...error) Strategy {
return func(attempts uint, err error) bool {
for _, e := range nonRetriableErrors {
if errors.Is(err, e) {
return false
}
}
return true
}
}
// Backoff returns a strategy that will delay the next retry, provided the
// action resulted in an error. The returned strategy will cause the caller
// (the retrier) to sleep.
func Backoff(strategy backoff.Strategy, maxBackoff time.Duration) Strategy {
return func(attempts uint, err error) bool {
delay := strategy(attempts)
cappedDelay := time.Duration(math.Min(float64(maxBackoff), float64(delay)))
sleeperImpl.Sleep(cappedDelay)
return true
}
}
// BackoffWithJitter returns a strategy similar to Backoff, but induces a jitter
// on the total delay. The maxBackoff is calculated before the jitter.
//
// The jitter parameter is a percentage of the total delay (after capping) that
// the timing can be off of. For example, a capped delay of 100ms with a jitter
// of 0.1 will result in a delay of 100ms +/- 10ms.
func BackoffWithJitter(strategy backoff.Strategy, maxBackoff time.Duration, jitter float64) Strategy {
return func(attempts uint, err error) bool {
delay := strategy(attempts)
cappedDelay := time.Duration(math.Min(float64(maxBackoff), float64(delay)))
// Center the jitter around the capped delay:
// <------cappedDelay------>
// jitter jitter
cappedDelayWithJitter := time.Duration(float64(cappedDelay) * (1 + (rand.Float64()*jitter*2 - jitter)))
sleeperImpl.Sleep(cappedDelayWithJitter)
return true
}
}
// RetriableGRPCCodes returns a strategy that specifies which GRPC status codes can be retried.
func RetriableGRPCCodes(retriableCodes ...codes.Code) Strategy {
return func(attempts uint, err error) bool {
code := status.Code(err)
for _, c := range retriableCodes {
if code == c {
return true
}
}
return false
}
}
// NonRetriableGRPCCodes returns a strategy that specifies which GRPC status codes should not be retried.
func NonRetriableGRPCCodes(nonRetriableCodes ...codes.Code) Strategy {
return func(attempts uint, err error) bool {
code := status.Code(err)
for _, c := range nonRetriableCodes {
if code == c {
return false
}
}
return true
}
}
type sleeper interface {
Sleep(time.Duration)
}
// realSleeper uses the time package to perform actual sleeps
type realSleeper struct{}
func (r *realSleeper) Sleep(d time.Duration) { time.Sleep(d) }
var sleeperImpl sleeper = &realSleeper{}