Skip to content

Commit 1d45711

Browse files
committed
Revert "retry: always include last operation error on timeout or context cancel (#181)"
This reverts commit 8801700.
1 parent 8801700 commit 1d45711

4 files changed

Lines changed: 3 additions & 95 deletions

File tree

CHANGELOG.md

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,6 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8-
## [6.0.0] - 2026-02-21
9-
10-
### Changed
11-
12-
- `Retry` now returns `errors.Join(context.Cause(ctx), lastErr)` when the context is cancelled, instead of returning only the context error. This ensures the last operation error is always available to the caller.
13-
- `Retry` now returns `errors.Join(context.DeadlineExceeded, lastErr)` when `WithMaxElapsedTime` is exceeded, instead of returning only the operation error. This makes timeout behaviour consistent regardless of whether the deadline comes from the context or `WithMaxElapsedTime`. (#181)
14-
15-
See [`ExampleRetry_outcomes`](example_test.go) for how to inspect the different error outcomes.
16-
178
## [5.0.0] - 2024-12-19
189

1910
### Added

example_test.go

Lines changed: 0 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"log"
88
"net/http"
99
"strconv"
10-
"time"
1110

1211
"github.com/cenkalti/backoff/v5"
1312
)
@@ -55,44 +54,6 @@ func ExampleRetry() {
5554
// Output: hello
5655
}
5756

58-
func ExampleRetry_outcomes() {
59-
operation := func() (string, error) {
60-
resp, err := http.Get("http://httpbin.org/get")
61-
if err != nil {
62-
return "", err
63-
}
64-
defer resp.Body.Close()
65-
return "ok", nil
66-
}
67-
68-
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
69-
defer cancel()
70-
71-
result, err := backoff.Retry(ctx, operation,
72-
backoff.WithMaxElapsedTime(10*time.Second),
73-
backoff.WithMaxTries(5),
74-
)
75-
76-
switch {
77-
case err == nil:
78-
// Operation succeeded.
79-
fmt.Println(result)
80-
81-
case errors.Is(err, context.Canceled):
82-
// Context was cancelled by the caller.
83-
fmt.Println("cancelled:", err)
84-
85-
case errors.Is(err, context.DeadlineExceeded):
86-
// Either the context deadline or WithMaxElapsedTime was reached.
87-
// err contains both the timeout signal and the last operation error.
88-
fmt.Println("timed out:", err)
89-
90-
default:
91-
// Retries exhausted (MaxTries or backoff stopped) or Permanent error.
92-
fmt.Println("retries exhausted:", err)
93-
}
94-
}
95-
9657
func ExampleTicker() {
9758
// An operation that may fail.
9859
operation := func() (string, error) {

retry.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func Retry[T any](ctx context.Context, operation Operation[T], opts ...RetryOpti
102102

103103
// Stop retrying if context is cancelled.
104104
if cerr := context.Cause(ctx); cerr != nil {
105-
return res, errors.Join(cerr, err)
105+
return res, cerr
106106
}
107107

108108
// Calculate next backoff duration.
@@ -120,7 +120,7 @@ func Retry[T any](ctx context.Context, operation Operation[T], opts ...RetryOpti
120120

121121
// Stop retrying if maximum elapsed time exceeded.
122122
if args.MaxElapsedTime > 0 && time.Since(startedAt)+next > args.MaxElapsedTime {
123-
return res, errors.Join(context.DeadlineExceeded, err)
123+
return res, err
124124
}
125125

126126
// Notify on error if a notifier function is provided.
@@ -133,7 +133,7 @@ func Retry[T any](ctx context.Context, operation Operation[T], opts ...RetryOpti
133133
select {
134134
case <-args.Timer.C():
135135
case <-ctx.Done():
136-
return res, errors.Join(context.Cause(ctx), err)
136+
return res, context.Cause(ctx)
137137
}
138138
}
139139
}

retry_test.go

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -121,50 +121,6 @@ func TestRetryContext(t *testing.T) {
121121
}
122122
}
123123

124-
// https://github.com/cenkalti/backoff/issues/181
125-
func TestRetryContextErrorIncludesOperationError(t *testing.T) {
126-
opErr := errors.New("operation error")
127-
ctxErr := errors.New("context error")
128-
129-
ctx, cancel := context.WithCancelCause(context.Background())
130-
defer cancel(nil)
131-
132-
i := 0
133-
f := func() (bool, error) {
134-
i++
135-
if i == 2 {
136-
cancel(ctxErr)
137-
}
138-
return false, opErr
139-
}
140-
141-
_, err := Retry(ctx, f, WithBackOff(NewConstantBackOff(time.Millisecond)), withTimer(&testTimer{}))
142-
if !errors.Is(err, ctxErr) {
143-
t.Errorf("context error not in result: %v", err)
144-
}
145-
if !errors.Is(err, opErr) {
146-
t.Errorf("operation error not in result: %v", err)
147-
}
148-
}
149-
150-
// https://github.com/cenkalti/backoff/issues/181
151-
func TestRetryMaxElapsedTimeErrorIncludesOperationError(t *testing.T) {
152-
opErr := errors.New("operation error")
153-
154-
_, err := Retry(
155-
context.Background(),
156-
func() (bool, error) { return false, opErr },
157-
WithMaxElapsedTime(time.Millisecond),
158-
withTimer(&testTimer{}),
159-
)
160-
if !errors.Is(err, context.DeadlineExceeded) {
161-
t.Errorf("DeadlineExceeded not in result: %v", err)
162-
}
163-
if !errors.Is(err, opErr) {
164-
t.Errorf("operation error not in result: %v", err)
165-
}
166-
}
167-
168124
func TestRetryPermanent(t *testing.T) {
169125
ensureRetries := func(test string, shouldRetry bool, f func() (int, error), expectRes int) {
170126
numRetries := -1

0 commit comments

Comments
 (0)