Skip to content

Commit 96d183b

Browse files
authored
Merge pull request #51 from slizco/slizco-add-opt-surface-work-error
Add a SurfaceWorkErrors() opt to the retrier
2 parents 6a6d489 + 029d178 commit 96d183b

File tree

2 files changed

+63
-6
lines changed

2 files changed

+63
-6
lines changed

retrier/retrier.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import (
1111
// Retrier implements the "retriable" resiliency pattern, abstracting out the process of retrying a failed action
1212
// a certain number of times with an optional back-off between each retry.
1313
type Retrier struct {
14-
backoff []time.Duration
15-
infiniteRetry bool
16-
class Classifier
17-
jitter float64
18-
rand *rand.Rand
19-
randMu sync.Mutex
14+
backoff []time.Duration
15+
infiniteRetry bool
16+
surfaceWorkErrors bool
17+
class Classifier
18+
jitter float64
19+
rand *rand.Rand
20+
randMu sync.Mutex
2021
}
2122

2223
// New constructs a Retrier with the given backoff pattern and classifier. The length of the backoff pattern
@@ -43,6 +44,13 @@ func (r *Retrier) WithInfiniteRetry() *Retrier {
4344
return r
4445
}
4546

47+
// WithSurfaceWorkErrors configures the retrier to always return the last error received from work function
48+
// even if a context timeout/deadline is hit.
49+
func (r *Retrier) WithSurfaceWorkErrors() *Retrier {
50+
r.surfaceWorkErrors = true
51+
return r
52+
}
53+
4654
// Run executes the given work function by executing RunCtx without context.Context.
4755
func (r *Retrier) Run(work func() error) error {
4856
return r.RunFn(context.Background(), func(c context.Context, r int) error {
@@ -83,6 +91,9 @@ func (r *Retrier) RunFn(ctx context.Context, work func(ctx context.Context, retr
8391

8492
timer := time.NewTimer(r.calcSleep(retries))
8593
if err := r.sleep(ctx, timer); err != nil {
94+
if r.surfaceWorkErrors {
95+
return ret
96+
}
8697
return err
8798
}
8899

retrier/retrier_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,52 @@ func TestRetrierRunFnWithInfinite(t *testing.T) {
153153
}
154154
}
155155

156+
func TestRetrierRunFnWithSurfaceWorkErrors(t *testing.T) {
157+
ctx, cancel := context.WithCancel(context.Background())
158+
defer cancel()
159+
r := New([]time.Duration{0, 10 * time.Millisecond}, nil).WithSurfaceWorkErrors()
160+
errExpected := []error{errFoo, errBar, errBaz}
161+
162+
err := r.RunFn(ctx, func(ctx context.Context, retries int) error {
163+
if retries >= len(errExpected) {
164+
return nil
165+
}
166+
if retries == 1 {
167+
// Context canceled inside second call to work function.
168+
cancel()
169+
}
170+
err := errExpected[retries]
171+
retries++
172+
return err
173+
})
174+
if err != errBar {
175+
t.Error(err)
176+
}
177+
}
178+
179+
func TestRetrierRunFnWithoutSurfaceWorkErrors(t *testing.T) {
180+
ctx, cancel := context.WithCancel(context.Background())
181+
defer cancel()
182+
r := New([]time.Duration{0, 10 * time.Millisecond}, nil)
183+
errExpected := []error{errFoo, errBar, errBaz}
184+
185+
err := r.RunFn(ctx, func(ctx context.Context, retries int) error {
186+
if retries >= len(errExpected) {
187+
return nil
188+
}
189+
if retries == 1 {
190+
// Context canceled inside second call to work function.
191+
cancel()
192+
}
193+
err := errExpected[retries]
194+
retries++
195+
return err
196+
})
197+
if err != context.Canceled {
198+
t.Error(err)
199+
}
200+
}
201+
156202
func TestRetrierNone(t *testing.T) {
157203
r := New(nil, nil)
158204

0 commit comments

Comments
 (0)