-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
retry.go
99 lines (87 loc) · 2.26 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
package retry
import (
"context"
"fmt"
)
// Action defines a callable function that package retry can handle.
type Action = func(context.Context) error
// A Breaker carries a cancellation signal to interrupt an action execution.
//
// It is a subset of the built-in context and github.com/kamilsk/breaker interfaces.
type Breaker = interface {
// Done returns a channel that's closed when a cancellation signal occurred.
Done() <-chan struct{}
// If Done is not yet closed, Err returns nil.
// If Done is closed, Err returns a non-nil error.
// After Err returns a non-nil error, successive calls to Err return the same error.
Err() error
}
// How is an alias for batch of Strategies.
//
// how := retry.How{
// strategy.Limit(3),
// }
//
type How = []func(Breaker, uint, error) bool
// Do takes the action and performs it, repetitively, until successful.
//
// Optionally, strategies may be passed that assess whether or not an attempt
// should be made.
func Do(
breaker Breaker,
action func(context.Context) error,
strategies ...func(Breaker, uint, error) bool,
) error {
var (
ctx = convert(breaker)
err error = internal
core error
)
for attempt, should := uint(0), true; should; attempt++ {
core = unwrap(err)
for i, repeat := 0, len(strategies); should && i < repeat; i++ {
should = should && strategies[i](breaker, attempt, core)
}
select {
case <-breaker.Done():
return breaker.Err()
default:
if should {
err = action(ctx)
}
}
should = should && err != nil
}
return err
}
// Go takes the action and performs it, repetitively, until successful.
// It differs from the Do method in that it performs the action in a goroutine.
//
// Optionally, strategies may be passed that assess whether or not an attempt
// should be made.
func Go(
breaker Breaker,
action func(context.Context) error,
strategies ...func(Breaker, uint, error) bool,
) error {
done := make(chan error, 1)
go func() {
defer func() {
if r := recover(); r != nil {
err, ok := r.(error)
if !ok {
err = fmt.Errorf("retry: unexpected panic: %#v", r)
}
done <- err
}
close(done)
}()
done <- Do(breaker, action, strategies...)
}()
select {
case <-breaker.Done():
return breaker.Err()
case err := <-done:
return err
}
}