...
1
2
3
4
5 package resty
6
7 import (
8 "context"
9 "math"
10 "math/rand"
11 "sync"
12 "time"
13 )
14
15 const (
16 defaultMaxRetries = 3
17 defaultWaitTime = time.Duration(100) * time.Millisecond
18 defaultMaxWaitTime = time.Duration(2000) * time.Millisecond
19 )
20
21 type (
22
23 Option func(*Options)
24
25
26
27 RetryConditionFunc func(*Response, error) bool
28
29
30 OnRetryFunc func(*Response, error)
31
32
33
34
35
36
37 RetryAfterFunc func(*Client, *Response) (time.Duration, error)
38
39
40 Options struct {
41 maxRetries int
42 waitTime time.Duration
43 maxWaitTime time.Duration
44 retryConditions []RetryConditionFunc
45 retryHooks []OnRetryFunc
46 }
47 )
48
49
50 func Retries(value int) Option {
51 return func(o *Options) {
52 o.maxRetries = value
53 }
54 }
55
56
57 func WaitTime(value time.Duration) Option {
58 return func(o *Options) {
59 o.waitTime = value
60 }
61 }
62
63
64 func MaxWaitTime(value time.Duration) Option {
65 return func(o *Options) {
66 o.maxWaitTime = value
67 }
68 }
69
70
71 func RetryConditions(conditions []RetryConditionFunc) Option {
72 return func(o *Options) {
73 o.retryConditions = conditions
74 }
75 }
76
77
78 func RetryHooks(hooks []OnRetryFunc) Option {
79 return func(o *Options) {
80 o.retryHooks = hooks
81 }
82 }
83
84
85
86 func Backoff(operation func() (*Response, error), options ...Option) error {
87
88 opts := Options{
89 maxRetries: defaultMaxRetries,
90 waitTime: defaultWaitTime,
91 maxWaitTime: defaultMaxWaitTime,
92 retryConditions: []RetryConditionFunc{},
93 }
94
95 for _, o := range options {
96 o(&opts)
97 }
98
99 var (
100 resp *Response
101 err error
102 )
103
104 for attempt := 0; attempt <= opts.maxRetries; attempt++ {
105 resp, err = operation()
106 ctx := context.Background()
107 if resp != nil && resp.Request.ctx != nil {
108 ctx = resp.Request.ctx
109 }
110 if ctx.Err() != nil {
111 return err
112 }
113
114 err1 := unwrapNoRetryErr(err)
115 needsRetry := err != nil && err == err1
116
117 for _, condition := range opts.retryConditions {
118 needsRetry = condition(resp, err1)
119 if needsRetry {
120 break
121 }
122 }
123
124 if !needsRetry {
125 return err
126 }
127
128 for _, hook := range opts.retryHooks {
129 hook(resp, err)
130 }
131
132
133
134 if attempt == opts.maxRetries {
135 return err
136 }
137
138 waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt)
139 if err2 != nil {
140 if err == nil {
141 err = err2
142 }
143 return err
144 }
145
146 select {
147 case <-time.After(waitTime):
148 case <-ctx.Done():
149 return ctx.Err()
150 }
151 }
152
153 return err
154 }
155
156 func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) {
157 const maxInt = 1<<31 - 1
158 if max < 0 {
159 max = maxInt
160 }
161 if resp == nil {
162 return jitterBackoff(min, max, attempt), nil
163 }
164
165 retryAfterFunc := resp.Request.client.RetryAfter
166
167
168 if retryAfterFunc == nil {
169 return jitterBackoff(min, max, attempt), nil
170 }
171
172 result, err := retryAfterFunc(resp.Request.client, resp)
173 if err != nil {
174 return 0, err
175 }
176 if result == 0 {
177 return jitterBackoff(min, max, attempt), nil
178 }
179 if result < 0 || max < result {
180 result = max
181 }
182 if result < min {
183 result = min
184 }
185 return result, nil
186 }
187
188
189
190 func jitterBackoff(min, max time.Duration, attempt int) time.Duration {
191 base := float64(min)
192 capLevel := float64(max)
193
194 temp := math.Min(capLevel, base*math.Exp2(float64(attempt)))
195 ri := time.Duration(temp / 2)
196 result := randDuration(ri)
197
198 if result < min {
199 result = min
200 }
201
202 return result
203 }
204
205 var rnd = newRnd()
206 var rndMu sync.Mutex
207
208 func randDuration(center time.Duration) time.Duration {
209 rndMu.Lock()
210 defer rndMu.Unlock()
211
212 var ri = int64(center)
213 var jitter = rnd.Int63n(ri)
214 return time.Duration(math.Abs(float64(ri + jitter)))
215 }
216
217 func newRnd() *rand.Rand {
218 var seed = time.Now().UnixNano()
219 var src = rand.NewSource(seed)
220 return rand.New(src)
221 }
222
View as plain text