Skip to content

Commit

Permalink
Add Backoff.ErrCause() (#538)
Browse files Browse the repository at this point in the history
* Backoff: change Err() to return the context cause when available

Signed-off-by: Marco Pracucci <[email protected]>

* Add Backoff.ErrCause() instead

Signed-off-by: Marco Pracucci <[email protected]>

* Renamed unit test

Signed-off-by: Marco Pracucci <[email protected]>

---------

Signed-off-by: Marco Pracucci <[email protected]>
  • Loading branch information
pracucci authored Jul 4, 2024
1 parent cef0e39 commit 97b2aa9
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
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())
})
}
}

0 comments on commit 97b2aa9

Please sign in to comment.