Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion retrier/classifier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type wrappedErr struct {
}

func (w wrappedErr) Error() string {
return "there's an error happening during X: " + w.Error()
return "there's an error happening during X: " + w.error.Error()
}

func (w wrappedErr) Unwrap() error {
Expand Down
19 changes: 19 additions & 0 deletions retrier/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package retrier

import "time"

type errWithBackoff struct {
err error
backoff time.Duration
}

func ErrWithBackoff(err error, backoff time.Duration) error {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add godocs for any new public types/methods/etc.

return &errWithBackoff{
err: err,
backoff: backoff,
}
}

func (e *errWithBackoff) Error() string {
return e.err.Error()
}
11 changes: 10 additions & 1 deletion retrier/retrier.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package retrier

import (
"context"
"errors"
"math/rand"
"sync"
"time"
Expand Down Expand Up @@ -89,7 +90,15 @@ func (r *Retrier) RunFn(ctx context.Context, work func(ctx context.Context, retr
return ret
}

timer := time.NewTimer(r.calcSleep(retries))
var err *errWithBackoff
var backoff time.Duration
if errors.As(ret, &err) {
backoff = err.backoff
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the response gives us a backoff value, does it still make sense to add some jitter to that? I'm not honestly sure...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with eapache's point.
I don't think Jitter should be unnecessarily considered by the client when given by Retry-After.
The client should add the Jitter because it is the same program or could be executed at the same time, and the Retry-After reverses the timing decision.

} else {
backoff = r.calcSleep(retries)
}

timer := time.NewTimer(backoff)
if err := r.sleep(ctx, timer); err != nil {
if r.surfaceWorkErrors {
return ret
Expand Down
18 changes: 18 additions & 0 deletions retrier/retrier_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,24 @@ func TestRetrierRunFnWithInfinite(t *testing.T) {
}
}

func TestRetrierWithDynamicBackoff(t *testing.T) {
r := New([]time.Duration{0, 10 * time.Millisecond}, nil)
st := time.Now()

err := r.Run(genWork([]error{ErrWithBackoff(errFoo, 500*time.Millisecond)}))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer to swap these times (so the retrier defaults to 500ms and the error overrides it to 10ms), so the test runs that much faster.

if err != nil {
t.Error(err)
}
if i != 2 {
t.Error("run wrong number of times")
}

if time.Since(st) < 500*time.Millisecond {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tried to avoid doing time-based assertions in the test suite, they tend to be quite flaky.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the test results may be flaky, but I couldn't think of any other way to check 🤔

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, I don't assert on how long it actually sleeps (I trust go's timer to work) and I simply unit-test the return value of calcSleep (and then the bigger tests just assert on the number of times that the method gets retried, which should be deterministic).

Perhaps for this PR you could pass err into calcSleep and move the logic into that method, so you can test that logic in a unit test of that method without any time-based assertions?

t.Error("not wait dynamic backoff")
}

}

func TestRetrierRunFnWithSurfaceWorkErrors(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
Expand Down
Loading