-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathretry.go
137 lines (119 loc) · 2.83 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
package retry
import (
"math"
"sync/atomic"
"time"
)
//Action defines an action to retry against
type Action func() (interface{}, error)
//Condition defines the retry loop stopping condition
type Condition func(interface{}, error) bool
//BackoffStrategy returns the delay to apply between attempt and attempt + 1
type BackoffStrategy func(attempt uint64, interval time.Duration) time.Duration
//Retry defines necessary attributes to retry an action
type Retry struct {
Action Action
Condition Condition
Duration time.Duration
Interval time.Duration
MaxAtt uint64
BackoffStrategy BackoffStrategy
timeout bool
lastValue interface{}
lastError error
attempt uint64
}
//Result stores the result of the retry loop
type Result struct {
Timeout bool
LastValue interface{}
LastError error
Attempts uint64
}
//With set the action to retry
func With(action Action) *Retry {
return &Retry{
Action: action,
Condition: FalseCondition,
attempt: 0,
MaxAtt: math.MaxUint64,
BackoffStrategy: UniformStrategy,
Duration: time.Second * math.MaxInt32,
}
}
//Until set the stopping condition
func (r *Retry) Until(condition Condition) *Retry {
r.Condition = condition
return r
}
//Every set the retry interval
func (r *Retry) Every(duration time.Duration) *Retry {
r.Interval = duration
return r
}
//For set the maximum duration of the retry loop
func (r *Retry) For(duration time.Duration) *Retry {
r.Duration = duration
return r
}
//MaxAttempts set the maximum attempts of the retry loop
func (r *Retry) MaxAttempts(max uint64) *Retry {
r.MaxAtt = max
return r
}
//WithBackoff set the backoff strategy to implement
func (r *Retry) WithBackoff(strategy BackoffStrategy) *Retry {
r.BackoffStrategy = strategy
return r
}
//Go starts the retry loop
func (r *Retry) Go() *Result {
end := make(chan bool, 1)
var stop atomic.Value
stop.Store(false)
go r.loop(end, &stop)
select {
case <-end:
break
case <-time.After(r.Duration):
stop.Store(true)
r.timeout = true
}
return &Result{
Timeout: r.timeout,
LastValue: r.lastValue,
LastError: r.lastError,
Attempts: r.attempt,
}
}
func (r *Retry) actionWrapper(stop *atomic.Value) {
if stop.Load().(bool) {
return
}
r.lastValue, r.lastError = r.Action()
if r.Condition(r.lastValue, r.lastError) {
stop.Store(true)
}
}
func (r *Retry) loop(end chan bool, stop *atomic.Value) {
for {
if r.attempt >= r.MaxAtt {
end <- true
break
}
if stop.Load().(bool) {
end <- true
break
}
go r.actionWrapper(stop)
d := r.BackoffStrategy(r.attempt, r.Interval)
r.attempt++
time.Sleep(d)
}
}
//NoError NoError wraps a function that do not return error
func NoError(action func() interface{}) Action {
return func() (interface{}, error) {
return action(), nil
}
}