-
Notifications
You must be signed in to change notification settings - Fork 1
/
retry.go
142 lines (127 loc) · 3.56 KB
/
retry.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
// Package retry implements frequently used retry strategies and options.
package retry
import "time"
// Stop is used to indicate the retry function to stop retry.
type Stop struct {
Err error
}
func (e Stop) Error() string {
return e.Err.Error()
}
// Default will call param function f at most 3 times before returning error.
// Between each retry will sleep an exponential time starts at 500ms.
// In case of all retry fails, the total sleep time will be about 750ms - 2250ms.
//
// It is shorthand for Retry(3, 500*time.Millisecond, f).
func Default(f func() error, opts ...Option) Result {
return Retry(3, 500*time.Millisecond, f, opts...)
}
// Retry retry the target function with exponential sleep time.
// It implements algorithm described in https://upgear.io/blog/simple-golang-retry-function/.
func Retry(attempts int, sleep time.Duration, f func() error, opts ...Option) Result {
opt := defaultOptions
opt.Attempts = attempts
opt.Sleep = sleep
return retry(opt, f, opts...)
}
// Const retry the target function with constant sleep time.
// It is shorthand for Retry(attempts, sleep, f, C()).
func Const(attempts int, sleep time.Duration, f func() error, opts ...Option) Result {
opt := defaultOptions
opt.Attempts = attempts
opt.Sleep = sleep
opt.Strategy = constant
return retry(opt, f, opts...)
}
// Linear retry the target function with linear sleep time.
// It is shorthand for Retry(attempts, sleep, f, L(sleep)).
func Linear(attempts int, sleep time.Duration, f func() error, opts ...Option) Result {
opt := defaultOptions
opt.Attempts = attempts
opt.Sleep = sleep
opt.Strategy = linear{sleep}.next
return retry(opt, f, opts...)
}
// Forever retry the target function endlessly if it returns error.
// To stop the the retry loop on error, the target function should return Stop.
//
// The caller should take care of dead loop.
func Forever(sleep, maxSleep time.Duration, f func() error, opts ...Option) Result {
opt := defaultOptions
opt.Sleep = sleep
opt.MaxSleep = maxSleep
return retry(opt, f, opts...)
}
// retry do the retry job according given options.
func retry(opt options, f func() error, opts ...Option) (r Result) {
for _, o := range opts {
opt = o(opt)
}
var err error
r.Attempts++
if err = f(); err == nil {
r.Ok = true
if opt.Breaker != nil {
opt.Breaker.succ.incr(time.Now().Unix())
}
return r
}
var merr = NewSizedError(opt.MaxErrors)
var sleep = opt.Sleep
for {
if _, ok := err.(Stop); ok {
break
}
// attempts <= 0 means retry forever.
if opt.Attempts > 0 && r.Attempts >= opt.Attempts {
break
}
// check sliding window breaker
if opt.Breaker != nil && !opt.Breaker.shouldRetry() {
break
}
merr.Append(err)
opt.Hook(r.Attempts, err)
if opt.Breaker != nil {
opt.Breaker.fail.incr(time.Now().Unix())
}
if opt.MaxSleep > 0 && sleep > opt.MaxSleep {
sleep = opt.MaxSleep
}
if opt.Jitter == nil {
time.Sleep(sleep)
} else {
time.Sleep(opt.Jitter(sleep))
}
r.Attempts++
if err = f(); err == nil {
r.Ok = true
if opt.Breaker != nil {
opt.Breaker.succ.incr(time.Now().Unix())
}
break
}
sleep = opt.Strategy(sleep)
}
if err != nil {
if s, ok := err.(Stop); ok {
// Return the original error for later checking.
merr.Append(s.Err)
opt.Hook(r.Attempts, s.Err)
// Stop error from caller don't count for circuit breaker.
} else {
merr.Append(err)
opt.Hook(r.Attempts, err)
if opt.Breaker != nil {
opt.Breaker.fail.incr(time.Now().Unix())
}
}
}
r.Error = merr.ErrOrNil()
return r
}
type Result struct {
Ok bool
Attempts int
Error error
}