-
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathrepeater.go
99 lines (86 loc) · 2.45 KB
/
repeater.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
// Package repeater implements retry functionality with different strategies.
// It provides fixed delays and various backoff strategies (constant, linear, exponential) with jitter support.
// The package allows custom retry strategies and error-specific handling. Context-aware implementation
// supports cancellation and timeouts.
package repeater
import (
"context"
"errors"
"time"
)
// ErrAny is a special sentinel error that, when passed as a critical error to Do,
// makes it fail on any error from the function
var ErrAny = errors.New("any error")
// Repeater holds configuration for retry operations
type Repeater struct {
strategy Strategy
attempts int
}
// NewWithStrategy creates a repeater with a custom retry strategy
func NewWithStrategy(attempts int, strategy Strategy) *Repeater {
if attempts <= 0 {
attempts = 1
}
if strategy == nil {
strategy = NewFixedDelay(time.Second)
}
return &Repeater{
attempts: attempts,
strategy: strategy,
}
}
// NewBackoff creates a repeater with backoff strategy
// Default settings (can be overridden with options):
// - 30s max delay
// - exponential backoff
// - 10% jitter
func NewBackoff(attempts int, initial time.Duration, opts ...backoffOption) *Repeater {
return NewWithStrategy(attempts, newBackoff(initial, opts...))
}
// NewFixed creates a repeater with fixed delay strategy
func NewFixed(attempts int, delay time.Duration) *Repeater {
return NewWithStrategy(attempts, NewFixedDelay(delay))
}
// Do repeats fun until it succeeds or max attempts reached
// terminates immediately on context cancellation or if err matches any in termErrs.
// if errs contains ErrAny, terminates on any error.
func (r *Repeater) Do(ctx context.Context, fun func() error, termErrs ...error) error {
var lastErr error
inErrors := func(err error) bool {
for _, e := range termErrs {
if errors.Is(e, ErrAny) {
return true
}
if errors.Is(err, e) {
return true
}
}
return false
}
for attempt := 0; attempt < r.attempts; attempt++ {
// check context before each attempt
if err := ctx.Err(); err != nil {
return err
}
var err error
if err = fun(); err == nil {
return nil
}
lastErr = err
if inErrors(err) {
return err
}
// don't sleep after the last attempt
if attempt < r.attempts-1 {
delay := r.strategy.NextDelay(attempt + 1)
if delay > 0 {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(delay):
}
}
}
}
return lastErr
}