-
-
Notifications
You must be signed in to change notification settings - Fork 14
/
retry.go
109 lines (96 loc) · 2.53 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
// Package retry provides the most advanced interruptible mechanism
// to perform actions repetitively until successful.
package retry
import "sync/atomic"
// Retry takes an action and performs it, repetitively, until successful.
// When it is done it releases resources associated with the Breaker.
//
// Optionally, strategies may be passed that assess whether or not an attempt
// should be made.
//
// Deprecated: will be replaced by Do function (current Try).
// TODO:v5 will be removed
func Retry(
breaker BreakCloser,
action func(attempt uint) error,
strategies ...func(attempt uint, err error) bool,
) error {
err := retry(breaker, action, strategies...)
breaker.Close()
return err
}
// Try takes an action and performs it, repetitively, until successful.
//
// Optionally, strategies may be passed that assess whether or not an attempt
// should be made.
//
// TODO:v5 will be renamed to Do
func Try(
breaker Breaker,
action func(attempt uint) error,
strategies ...func(attempt uint, err error) bool,
) error {
return retry(breaker, action, strategies...)
}
func retry(
breaker Breaker,
action func(attempt uint) error,
strategies ...func(attempt uint, err error) bool,
) error {
var interrupted uint32
done := make(chan result, 1)
go func(breaker *uint32) {
var err error
defer func() {
done <- result{err, recover()}
close(done)
}()
for attempt := uint(0); shouldAttempt(breaker, attempt, err, strategies...); attempt++ {
err = action(attempt)
}
}(&interrupted)
select {
case <-breaker.Done():
atomic.StoreUint32(&interrupted, 1)
return Interrupted
case err := <-done:
if _, is := IsRecovered(err); is {
return err
}
return err.error
}
}
// shouldAttempt evaluates the provided strategies with the given attempt to
// determine if the Retry loop should make another attempt.
func shouldAttempt(breaker *uint32, attempt uint, err error, strategies ...func(uint, error) bool) bool {
err = unwrap(err)
should := attempt == 0 || err != nil
for i, repeat := 0, len(strategies); should && i < repeat; i++ {
should = should && strategies[i](attempt, err)
}
return should && atomic.LoadUint32(breaker) == 0
}
func unwrap(err error) error {
// compatible with github.com/pkg/errors
type causer interface {
Cause() error
}
// compatible with built-in errors since 1.13
type wrapper interface {
Unwrap() error
}
for err != nil {
layer, is := err.(wrapper)
if is {
err = layer.Unwrap()
continue
}
cause, is := err.(causer)
if is {
err = cause.Cause()
continue
}
break
}
return err
}