Skip to content

Commit 3d3869e

Browse files
committed
v5
1 parent 66b23f9 commit 3d3869e

12 files changed

+121
-470
lines changed

context.go

-62
This file was deleted.

context_test.go

-25
This file was deleted.

error.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package backoff
2+
3+
// PermanentError signals that the operation should not be retried.
4+
type PermanentError struct {
5+
Err error
6+
}
7+
8+
// Permanent wraps the given err in a *PermanentError.
9+
func Permanent(err error) error {
10+
if err == nil {
11+
return nil
12+
}
13+
return &PermanentError{
14+
Err: err,
15+
}
16+
}
17+
18+
func (e *PermanentError) Error() string {
19+
return e.Err.Error()
20+
}
21+
22+
func (e *PermanentError) Unwrap() error {
23+
return e.Err
24+
}

example_test.go

+3-23
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,11 @@ import (
77

88
func ExampleRetry() {
99
// An operation that may fail.
10-
operation := func() error {
11-
return nil // or an error
10+
operation := func() (bool, error) {
11+
return true, nil
1212
}
1313

14-
err := Retry(operation, NewExponentialBackOff())
15-
if err != nil {
16-
// Handle error.
17-
return
18-
}
19-
20-
// Operation is successful.
21-
}
22-
23-
func ExampleRetryContext() { // nolint: govet
24-
// A context
25-
ctx := context.Background()
26-
27-
// An operation that may fail.
28-
operation := func() error {
29-
return nil // or an error
30-
}
31-
32-
b := WithContext(NewExponentialBackOff(), ctx)
33-
34-
err := Retry(operation, b)
14+
_, err := Retry(context.TODO(), operation, WithBackOff(NewExponentialBackOff()))
3515
if err != nil {
3616
// Handle error.
3717
return

exponential.go

+6-90
Original file line numberDiff line numberDiff line change
@@ -56,131 +56,47 @@ type ExponentialBackOff struct {
5656
RandomizationFactor float64
5757
Multiplier float64
5858
MaxInterval time.Duration
59-
// After MaxElapsedTime the ExponentialBackOff stops.
60-
// It never stops if MaxElapsedTime == 0.
61-
MaxElapsedTime time.Duration
62-
Clock Clock
6359

6460
currentInterval time.Duration
65-
startTime time.Time
6661
}
6762

68-
// Clock is an interface that returns current time for BackOff.
69-
type Clock interface {
70-
Now() time.Time
71-
}
72-
73-
// ExponentialBackOffOpts is a function type used to configure ExponentialBackOff options.
74-
type ExponentialBackOffOpts func(*ExponentialBackOff)
75-
7663
// Default values for ExponentialBackOff.
7764
const (
7865
DefaultInitialInterval = 500 * time.Millisecond
7966
DefaultRandomizationFactor = 0.5
8067
DefaultMultiplier = 1.5
8168
DefaultMaxInterval = 60 * time.Second
82-
DefaultMaxElapsedTime = 15 * time.Minute
8369
)
8470

8571
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
86-
func NewExponentialBackOff(opts ...ExponentialBackOffOpts) *ExponentialBackOff {
87-
b := &ExponentialBackOff{
72+
func NewExponentialBackOff() *ExponentialBackOff {
73+
return &ExponentialBackOff{
8874
InitialInterval: DefaultInitialInterval,
8975
RandomizationFactor: DefaultRandomizationFactor,
9076
Multiplier: DefaultMultiplier,
9177
MaxInterval: DefaultMaxInterval,
92-
MaxElapsedTime: DefaultMaxElapsedTime,
93-
Clock: SystemClock,
94-
}
95-
for _, fn := range opts {
96-
fn(b)
97-
}
98-
b.Reset()
99-
return b
100-
}
101-
102-
// WithInitialInterval sets the initial interval between retries.
103-
func WithInitialInterval(duration time.Duration) ExponentialBackOffOpts {
104-
return func(ebo *ExponentialBackOff) {
105-
ebo.InitialInterval = duration
106-
}
107-
}
108-
109-
// WithRandomizationFactor sets the randomization factor to add jitter to intervals.
110-
func WithRandomizationFactor(randomizationFactor float64) ExponentialBackOffOpts {
111-
return func(ebo *ExponentialBackOff) {
112-
ebo.RandomizationFactor = randomizationFactor
113-
}
114-
}
115-
116-
// WithMultiplier sets the multiplier for increasing the interval after each retry.
117-
func WithMultiplier(multiplier float64) ExponentialBackOffOpts {
118-
return func(ebo *ExponentialBackOff) {
119-
ebo.Multiplier = multiplier
120-
}
121-
}
122-
123-
// WithMaxInterval sets the maximum interval between retries.
124-
func WithMaxInterval(duration time.Duration) ExponentialBackOffOpts {
125-
return func(ebo *ExponentialBackOff) {
126-
ebo.MaxInterval = duration
12778
}
12879
}
12980

130-
// WithMaxElapsedTime sets the maximum total time for retries.
131-
func WithMaxElapsedTime(duration time.Duration) ExponentialBackOffOpts {
132-
return func(ebo *ExponentialBackOff) {
133-
ebo.MaxElapsedTime = duration
134-
}
135-
}
136-
137-
// WithClockProvider sets the clock used to measure time.
138-
func WithClockProvider(clock Clock) ExponentialBackOffOpts {
139-
return func(ebo *ExponentialBackOff) {
140-
ebo.Clock = clock
141-
}
142-
}
143-
144-
type systemClock struct{}
145-
146-
func (t systemClock) Now() time.Time {
147-
return time.Now()
148-
}
149-
150-
// SystemClock implements Clock interface that uses time.Now().
151-
var SystemClock = systemClock{}
152-
15381
// Reset the interval back to the initial retry interval and restarts the timer.
15482
// Reset must be called before using b.
15583
func (b *ExponentialBackOff) Reset() {
15684
b.currentInterval = b.InitialInterval
157-
b.startTime = b.Clock.Now()
15885
}
15986

16087
// NextBackOff calculates the next backoff interval using the formula:
16188
//
16289
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
16390
func (b *ExponentialBackOff) NextBackOff() time.Duration {
164-
// Make sure we have not gone over the maximum elapsed time.
165-
elapsed := b.GetElapsedTime()
91+
if b.currentInterval == 0 {
92+
b.currentInterval = b.InitialInterval
93+
}
94+
16695
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
16796
b.incrementCurrentInterval()
168-
if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime {
169-
return Stop
170-
}
17197
return next
17298
}
17399

174-
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
175-
// is created and is reset when Reset() is called.
176-
//
177-
// The elapsed time is computed using time.Now().UnixNano(). It is
178-
// safe to call even while the backoff policy is used by a running
179-
// ticker.
180-
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
181-
return b.Clock.Now().Sub(b.startTime)
182-
}
183-
184100
// Increments the current interval by multiplying it with the multiplier.
185101
func (b *ExponentialBackOff) incrementCurrentInterval() {
186102
// Check for overflow, if overflow is detected set the current interval to the max interval.

exponential_test.go

-70
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ func TestBackOff(t *testing.T) {
1212
testRandomizationFactor = 0.1
1313
testMultiplier = 2.0
1414
testMaxInterval = 5 * time.Second
15-
testMaxElapsedTime = 15 * time.Minute
1615
)
1716

1817
exp := NewExponentialBackOff()
1918
exp.InitialInterval = testInitialInterval
2019
exp.RandomizationFactor = testRandomizationFactor
2120
exp.Multiplier = testMultiplier
2221
exp.MaxInterval = testMaxInterval
23-
exp.MaxElapsedTime = testMaxElapsedTime
2422
exp.Reset()
2523

2624
var expectedResults = []time.Duration{500, 1000, 2000, 4000, 5000, 5000, 5000, 5000, 5000, 5000}
@@ -52,37 +50,6 @@ func TestGetRandomizedInterval(t *testing.T) {
5250
assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.99, 2))
5351
}
5452

55-
type TestClock struct {
56-
i time.Duration
57-
start time.Time
58-
}
59-
60-
func (c *TestClock) Now() time.Time {
61-
t := c.start.Add(c.i)
62-
c.i += time.Second
63-
return t
64-
}
65-
66-
func TestGetElapsedTime(t *testing.T) {
67-
var exp = NewExponentialBackOff()
68-
exp.Clock = &TestClock{}
69-
exp.Reset()
70-
71-
var elapsedTime = exp.GetElapsedTime()
72-
if elapsedTime != time.Second {
73-
t.Errorf("elapsedTime=%d", elapsedTime)
74-
}
75-
}
76-
77-
func TestMaxElapsedTime(t *testing.T) {
78-
var exp = NewExponentialBackOff()
79-
exp.Clock = &TestClock{start: time.Time{}.Add(10000 * time.Second)}
80-
// Change the currentElapsedTime to be 0 ensuring that the elapsed time will be greater
81-
// than the max elapsed time.
82-
exp.startTime = time.Time{}
83-
assertEquals(t, Stop, exp.NextBackOff())
84-
}
85-
8653
func TestBackOffOverflow(t *testing.T) {
8754
var (
8855
testInitialInterval time.Duration = math.MaxInt64 / 2
@@ -106,40 +73,3 @@ func assertEquals(t *testing.T, expected, value time.Duration) {
10673
t.Errorf("got: %d, expected: %d", value, expected)
10774
}
10875
}
109-
110-
func TestNewExponentialBackOff(t *testing.T) {
111-
// Create a new ExponentialBackOff with custom options
112-
backOff := NewExponentialBackOff(
113-
WithInitialInterval(1*time.Second),
114-
WithMultiplier(2.0),
115-
WithMaxInterval(10*time.Second),
116-
WithMaxElapsedTime(30*time.Second),
117-
WithClockProvider(SystemClock),
118-
)
119-
120-
// Check that the backOff object is not nil
121-
if backOff == nil {
122-
t.Error("Expected a non-nil ExponentialBackOff object, got nil")
123-
}
124-
125-
// Check that the custom options were applied correctly
126-
if backOff.InitialInterval != 1*time.Second {
127-
t.Errorf("Expected InitialInterval to be 1 second, got %v", backOff.InitialInterval)
128-
}
129-
130-
if backOff.Multiplier != 2.0 {
131-
t.Errorf("Expected Multiplier to be 2.0, got %v", backOff.Multiplier)
132-
}
133-
134-
if backOff.MaxInterval != 10*time.Second {
135-
t.Errorf("Expected MaxInterval to be 10 seconds, got %v", backOff.MaxInterval)
136-
}
137-
138-
if backOff.MaxElapsedTime != 30*time.Second {
139-
t.Errorf("Expected MaxElapsedTime to be 30 seconds, got %v", backOff.MaxElapsedTime)
140-
}
141-
142-
if backOff.Clock != SystemClock {
143-
t.Errorf("Expected Clock to be SystemClock, got %v", backOff.Clock)
144-
}
145-
}

0 commit comments

Comments
 (0)