forked from redis/rueidis
-
Notifications
You must be signed in to change notification settings - Fork 0
/
retry.go
88 lines (74 loc) · 2.65 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
package rueidis
import (
"context"
"runtime"
"time"
"github.com/redis/rueidis/internal/util"
)
const (
defaultMaxRetries = 20
defaultMaxRetryDelay = 1 * time.Second
)
// RetryDelayFn returns the delay that should be used before retrying the
// attempt. Will return negative delay if the delay could not be determined or do not retry.
type RetryDelayFn func(attempts int, cmd Completed, err error) time.Duration
// defaultRetryDelayFn delays the next retry exponentially without considering the error.
// max delay is 1 second.
// This "Equal Jitter" delay produced by this implementation is not monotonic increasing. ref: https://aws.amazon.com/ko/blogs/architecture/exponential-backoff-and-jitter/
func defaultRetryDelayFn(attempts int, _ Completed, _ error) time.Duration {
base := 1 << min(defaultMaxRetries, attempts)
jitter := util.FastRand(base)
return min(defaultMaxRetryDelay, time.Duration(base+jitter)*time.Microsecond)
}
type retryHandler interface {
// RetryDelay returns the delay that should be used before retrying the
// attempt. Will return negative delay if the delay could not be determined or do
// not retry.
// If the delay is zero, the next retry should be attempted immediately.
RetryDelay(attempts int, cmd Completed, err error) time.Duration
// WaitForRetry waits until the next retry should be attempted.
WaitForRetry(ctx context.Context, duration time.Duration)
// WaitOrSkipRetry waits until the next retry should be attempted
// or returns false if the command should not be retried.
// Returns false immediately if the command should not be retried.
// Returns true after the delay if the command should be retried.
WaitOrSkipRetry(ctx context.Context, attempts int, cmd Completed, err error) bool
}
type retryer struct {
RetryDelayFn RetryDelayFn
}
var _ retryHandler = (*retryer)(nil)
func newRetryer(retryDelayFn RetryDelayFn) *retryer {
return &retryer{RetryDelayFn: retryDelayFn}
}
func (r *retryer) RetryDelay(attempts int, cmd Completed, err error) time.Duration {
return r.RetryDelayFn(attempts, cmd, err)
}
func (r *retryer) WaitForRetry(ctx context.Context, duration time.Duration) {
if duration > 0 {
if ch := ctx.Done(); ch != nil {
tm := time.NewTimer(duration)
defer tm.Stop()
select {
case <-ch:
case <-tm.C:
}
} else {
time.Sleep(duration)
}
}
}
func (r *retryer) WaitOrSkipRetry(
ctx context.Context, attempts int, cmd Completed, err error,
) bool {
if delay := r.RetryDelay(attempts, cmd, err); delay == 0 {
runtime.Gosched()
return true
} else if delay > 0 {
if dl, ok := ctx.Deadline(); !ok || time.Until(dl) > delay {
r.WaitForRetry(ctx, delay)
return true
}
}
return false
}