Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Backoff.ErrCause() #538

Merged
merged 3 commits into from
Jul 4, 2024
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
* [ENHANCEMENT] SpanProfiler: do less work on unsampled traces. #528
* [ENHANCEMENT] Log Middleware: if the trace is not sampled, log its ID as `trace_id_unsampled` instead of `trace_id`. #529
* [EHNANCEMENT] httpgrpc: httpgrpc Server can now use error message from special HTTP header when converting HTTP response to an error. This is useful when HTTP response body contains binary data that doesn't form valid utf-8 string, otherwise grpc would fail to marshal returned error. #531
* [CHANGE] Backoff: added `Backoff.ErrCause()` which is like `Backoff.Err()` but returns the context cause if backoff is terminated because the context has been canceled. #538
* [BUGFIX] spanlogger: Support multiple tenant IDs. #59
* [BUGFIX] Memberlist: fixed corrupted packets when sending compound messages with more than 255 messages or messages bigger than 64KB. #85
* [BUGFIX] Ring: `ring_member_ownership_percent` and `ring_tokens_owned` metrics are not updated on scale down. #109
Expand Down
11 changes: 10 additions & 1 deletion backoff/backoff.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func (b *Backoff) Ongoing() bool {
return b.ctx.Err() == nil && (b.cfg.MaxRetries == 0 || b.numRetries < b.cfg.MaxRetries)
}

// Err returns the reason for terminating the backoff, or nil if it didn't terminate
// Err returns the reason for terminating the backoff, or nil if it didn't terminate.
func (b *Backoff) Err() error {
if b.ctx.Err() != nil {
return b.ctx.Err()
Expand All @@ -65,6 +65,15 @@ func (b *Backoff) Err() error {
return nil
}

// ErrCause is like Err() but returns the context cause if backoff is terminated because the
// context has been canceled.
func (b *Backoff) ErrCause() error {
if b.ctx.Err() != nil {
return context.Cause(b.ctx)
}
return b.Err()
}

// NumRetries returns the number of retries so far
func (b *Backoff) NumRetries() int {
return b.numRetries
Expand Down
68 changes: 68 additions & 0 deletions backoff/backoff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"testing"
"time"

"github.com/pkg/errors"
"github.com/stretchr/testify/require"
)

func TestBackoff_NextDelay(t *testing.T) {
Expand Down Expand Up @@ -101,3 +104,68 @@ func TestBackoff_NextDelay(t *testing.T) {
})
}
}

func TestBackoff_ErrAndErrCause(t *testing.T) {
cause := errors.New("my cause")

tests := map[string]struct {
ctx func(*testing.T) context.Context
expectedErr error
expectedErrCause error
}{
"context deadline exceeded without cause": {
ctx: func(t *testing.T) context.Context {
ctx, cancel := context.WithDeadline(context.Background(), time.Now())
t.Cleanup(cancel)

return ctx
},
expectedErr: context.DeadlineExceeded,
expectedErrCause: context.DeadlineExceeded,
},
"context deadline exceeded with cause": {
ctx: func(t *testing.T) context.Context {
ctx, cancel := context.WithDeadlineCause(context.Background(), time.Now(), cause)
t.Cleanup(cancel)

return ctx
},
expectedErr: context.DeadlineExceeded,
expectedErrCause: cause,
},
"context is canceled without cause": {
ctx: func(_ *testing.T) context.Context {
ctx, cancel := context.WithCancel(context.Background())
cancel()

return ctx
},
expectedErr: context.Canceled,
expectedErrCause: context.Canceled,
},
"context is canceled with cause": {
ctx: func(_ *testing.T) context.Context {
ctx, cancel := context.WithCancelCause(context.Background())
cancel(cause)

return ctx
},
expectedErr: context.Canceled,
expectedErrCause: cause,
},
}

for testName, testData := range tests {
t.Run(testName, func(t *testing.T) {
b := New(testData.ctx(t), Config{})

// Wait until the backoff returns error.
require.Eventually(t, func() bool {
return b.Err() != nil
}, time.Second, 10*time.Millisecond)

require.Equal(t, testData.expectedErr, b.Err())
require.Equal(t, testData.expectedErrCause, b.ErrCause())
})
}
}
Loading