-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathretry.go
210 lines (171 loc) · 5.09 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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
package lo
import (
"sync"
"time"
)
type debounce struct {
after time.Duration
mu *sync.Mutex
timer *time.Timer
done bool
callbacks []func()
}
func (d *debounce) reset() *debounce {
d.mu.Lock()
defer d.mu.Unlock()
if d.done {
return d
}
if d.timer != nil {
d.timer.Stop()
}
d.timer = time.AfterFunc(d.after, func() {
for _, f := range d.callbacks {
f()
}
})
return d
}
func (d *debounce) cancel() {
d.mu.Lock()
defer d.mu.Unlock()
if d.timer != nil {
d.timer.Stop()
d.timer = nil
}
d.done = true
}
// NewDebounce creates a debounced instance that delays invoking functions given until after wait milliseconds have elapsed.
// Play: https://go.dev/play/p/mz32VMK2nqe
func NewDebounce(duration time.Duration, f ...func()) (func(), func()) {
d := &debounce{
after: duration,
mu: new(sync.Mutex),
timer: nil,
done: false,
callbacks: f,
}
return func() {
d.reset()
}, d.cancel
}
// Attempt invokes a function N times until it returns valid output. Returning either the caught error or nil. When first argument is less than `1`, the function runs until a successful response is returned.
// Play: https://go.dev/play/p/3ggJZ2ZKcMj
func Attempt(maxIteration int, f func(index int) error) (int, error) {
var err error
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
// for retries >= 0 {
err = f(i)
if err == nil {
return i + 1, nil
}
}
return maxIteration, err
}
// AttemptWithDelay invokes a function N times until it returns valid output,
// with a pause between each call. Returning either the caught error or nil.
// When first argument is less than `1`, the function runs until a successful
// response is returned.
// Play: https://go.dev/play/p/tVs6CygC7m1
func AttemptWithDelay(maxIteration int, delay time.Duration, f func(index int, duration time.Duration) error) (int, time.Duration, error) {
var err error
start := time.Now()
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
err = f(i, time.Since(start))
if err == nil {
return i + 1, time.Since(start), nil
}
if maxIteration <= 0 || i+1 < maxIteration {
time.Sleep(delay)
}
}
return maxIteration, time.Since(start), err
}
// AttemptWhile invokes a function N times until it returns valid output.
// Returning either the caught error or nil, and along with a bool value to identify
// whether it needs invoke function continuously. It will terminate the invoke
// immediately if second bool value is returned with falsy value. When first
// argument is less than `1`, the function runs until a successful response is
// returned.
func AttemptWhile(maxIteration int, f func(int) (error, bool)) (int, error) {
var err error
var shouldContinueInvoke bool
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
// for retries >= 0 {
err, shouldContinueInvoke = f(i)
if !shouldContinueInvoke { // if shouldContinueInvoke is false, then return immediately
return i + 1, err
}
if err == nil {
return i + 1, nil
}
}
return maxIteration, err
}
// AttemptWhileWithDelay invokes a function N times until it returns valid output,
// with a pause between each call. Returning either the caught error or nil, and along
// with a bool value to identify whether it needs to invoke function continuously.
// It will terminate the invoke immediately if second bool value is returned with falsy
// value. When first argument is less than `1`, the function runs until a successful
// response is returned.
func AttemptWhileWithDelay(maxIteration int, delay time.Duration, f func(int, time.Duration) (error, bool)) (int, time.Duration, error) {
var err error
var shouldContinueInvoke bool
start := time.Now()
for i := 0; maxIteration <= 0 || i < maxIteration; i++ {
err, shouldContinueInvoke = f(i, time.Since(start))
if !shouldContinueInvoke { // if shouldContinueInvoke is false, then return immediately
return i + 1, time.Since(start), err
}
if err == nil {
return i + 1, time.Since(start), nil
}
if maxIteration <= 0 || i+1 < maxIteration {
time.Sleep(delay)
}
}
return maxIteration, time.Since(start), err
}
type transactionStep[T any] struct {
exec func(T) (T, error)
onRollback func(T) T
}
// NewTransaction instanciate a new transaction.
func NewTransaction[T any]() *Transaction[T] {
return &Transaction[T]{
steps: []transactionStep[T]{},
}
}
// Transaction implements a Saga pattern
type Transaction[T any] struct {
steps []transactionStep[T]
}
// Then adds a step to the chain of callbacks. It returns the same Transaction.
func (t *Transaction[T]) Then(exec func(T) (T, error), onRollback func(T) T) *Transaction[T] {
t.steps = append(t.steps, transactionStep[T]{
exec: exec,
onRollback: onRollback,
})
return t
}
// Process runs the Transaction steps and rollbacks in case of errors.
func (t *Transaction[T]) Process(state T) (T, error) {
var i int
var err error
for i < len(t.steps) {
state, err = t.steps[i].exec(state)
if err != nil {
break
}
i++
}
if err == nil {
return state, nil
}
for i > 0 {
i--
state = t.steps[i].onRollback(state)
}
return state, err
}
// throttle ?