-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathretry.go
157 lines (130 loc) · 5.27 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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Operations retries with different strategies.
package retry
import (
"context"
"errors"
"time"
"github.com/gotidy/lib/ptr"
)
// Notify is a notify-on-error function. It receives an operation error and
// strategy delay if the operation failed (with an error).
//
// NOTE that if the strategy stated to stop retrying,
// the notify function isn't called.
type Notify func(err error, delay time.Duration, try int, elapsed time.Duration)
type options struct {
// MaxRetries is maximum count of retries.
MaxRetries int
// After Timeout the context will canceled.
Timeout time.Duration
// After MaxElapsedTime the retrying will stopped.
MaxElapsedTime time.Duration
// Notify
Notify Notify
Strategy Strategy
}
// Option is a retrying option setter.
type Option func(opts *options)
// WithMaxRetries sets maximum retries.
func WithMaxRetries(n int) Option {
return func(opts *options) {
opts.Strategy = MaxRetriesWrapper{MaxRetries: n}.Wrap(opts.Strategy)
}
}
// WithTimeout sets timeout.
func WithTimeout(d time.Duration) Option {
return func(opts *options) {
opts.Timeout = d
}
}
// WithMaxElapsedTime sets MaxElapsedTime option.
// Time after which retrying are stopped.
func WithMaxElapsedTime(d time.Duration) Option {
return func(opts *options) {
opts.Strategy = MaxElapsedTimeWrapper{MaxElapsedTime: d}.Wrap(opts.Strategy)
}
}
// WithNotify sets maximum retries.
func WithNotify(n Notify) Option {
return func(opts *options) {
opts.Notify = n
}
}
// DoR retries the operation with result and specified strategy.
// To stop the retry, the operation must return a permanent error, see Permanent(err).
func DoR[T any](ctx context.Context, strategy Strategy, operation func(ctx context.Context) (T, error), o ...Option) (result T, err error) {
opts := options{Strategy: strategy}
for _, opt := range o {
opt(&opts)
}
if opts.Timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, opts.Timeout)
defer cancel()
}
start := time.Now()
retrying := 1
var delay time.Duration
next := opts.Strategy.Iterator()
for {
if ctx.Err() != nil {
return ptr.Zero[T](), newError(err, ctx.Err(), "", retrying, delay, time.Since(start))
}
result, err = operation(ctx)
if err == nil {
return result, nil
}
var perm PermanentError
if ok := errors.As(err, &perm); ok {
return ptr.Zero[T](), perm
}
var nErr error
prevDelay := delay
delay, nErr = next()
elapsed := time.Since(start)
if delay == StopDelay {
return ptr.Zero[T](), newError(err, ctx.Err(), nErr.Error(), retrying, prevDelay, elapsed)
}
if opts.Notify != nil {
opts.Notify(err, delay, retrying, elapsed)
}
select {
case <-ctx.Done():
return ptr.Zero[T](), newError(err, ctx.Err(), "", retrying, delay, elapsed)
case <-time.After(delay):
}
retrying++
}
}
// DoRN retries the operation with result with specified strategy and the maximum number of retries.
func DoRN[T any](ctx context.Context, strategy Strategy, operation func(ctx context.Context) (T, error), maxReties int, o ...Option) (result T, err error) {
return DoR(ctx, strategy, operation, append(o, WithMaxRetries(maxReties))...)
}
// DoRE retries the operation with result with specified strategy and maximum elapsed time.
func DoRE[T any](ctx context.Context, strategy Strategy, operation func(ctx context.Context) (T, error), maxElapsedTime time.Duration, o ...Option) (result T, err error) {
return DoR(ctx, strategy, operation, append(o, WithMaxElapsedTime(maxElapsedTime))...)
}
// DoRNE retries the operation with result with the specified strategy and the maximum number of retries and maximum elapsed time.
func DoRNE[T any](ctx context.Context, strategy Strategy, operation func(ctx context.Context) (T, error), maxReties int, maxElapsedTime time.Duration, o ...Option) (result T, err error) {
return DoR(ctx, strategy, operation, append(o, WithMaxRetries(maxReties), WithMaxElapsedTime(maxElapsedTime))...)
}
// Do retries the operation with specified strategy.
// To stop the retry, the operation must return a permanent error, see Permanent(err).
func Do(ctx context.Context, strategy Strategy, operation func(ctx context.Context) error, o ...Option) (err error) {
_, err = DoR(ctx, strategy, func(ctx context.Context) (struct{}, error) {
return struct{}{}, operation(ctx)
}, o...)
return err
}
// DoN retries the operation with specified strategy and the maximum number of retries.
func DoN(ctx context.Context, strategy Strategy, operation func(ctx context.Context) error, maxReties int, o ...Option) (err error) {
return Do(ctx, strategy, operation, append(o, WithMaxRetries(maxReties))...)
}
// DoE retries the operation with the specified strategy and maximum elapsed time.
func DoE(ctx context.Context, strategy Strategy, operation func(ctx context.Context) error, maxElapsedTime time.Duration, o ...Option) (err error) {
return Do(ctx, strategy, operation, append(o, WithMaxElapsedTime(maxElapsedTime))...)
}
// DoNE retries the operation with the specified strategy and the maximum number of retries and maximum elapsed time.
func DoNE(ctx context.Context, strategy Strategy, operation func(ctx context.Context) error, maxReties int, maxElapsedTime time.Duration, o ...Option) (err error) {
return Do(ctx, strategy, operation, append(o, WithMaxRetries(maxReties), WithMaxElapsedTime(maxElapsedTime))...)
}