Skip to content

Commit

Permalink
breaking: panic instead of returning an error negative bases
Browse files Browse the repository at this point in the history
  • Loading branch information
sethvargo committed Jan 3, 2022
1 parent 2ee2801 commit e436cb1
Show file tree
Hide file tree
Showing 9 changed files with 96 additions and 159 deletions.
25 changes: 6 additions & 19 deletions README.md
Expand Up @@ -45,14 +45,13 @@ func main() {
}

ctx := context.Background()
err := retry.Fibonacci(ctx, 1*time.Second, func(ctx context.Context) error {
if err := retry.Fibonacci(ctx, 1*time.Second, func(ctx context.Context) error {
if err := db.PingContext(ctx); err != nil {
// This marks the error as retryable
return retry.RetryableError(err)
}
return nil
})
if err != nil {
}); err != nil {
log.Fatal(err)
}
}
Expand Down Expand Up @@ -121,10 +120,7 @@ To reduce the changes of a thundering herd, add random jitter to the returned
value.

```golang
b, err := NewFibonacci(1 * time.Second)
if err != nil {
// handle err
}
b := NewFibonacci(1 * time.Second)

// Return the next value, +/- 500ms
b = WithJitter(500*time.Millisecond, b)
Expand All @@ -139,10 +135,7 @@ To terminate a retry, specify the maximum number of _retries_. Note this
is _retries_, not _attempts_. Attempts is retries + 1.

```golang
b, err := NewFibonacci(1 * time.Second)
if err != nil {
// handle err
}
b := NewFibonacci(1 * time.Second)

// Stop after 4 retries, when the 5th attempt has failed. In this example, the worst case elapsed
// time would be 1s + 1s + 2s + 3s = 7s.
Expand All @@ -154,10 +147,7 @@ b = WithMaxRetries(4, b)
To ensure an individual calculated duration never exceeds a value, use a cap:

```golang
b, err := NewFibonacci(1 * time.Second)
if err != nil {
// handle err
}
b := NewFibonacci(1 * time.Second)

// Ensure the maximum value is 2s. In this example, the sleep values would be
// 1s, 1s, 2s, 2s, 2s, 2s...
Expand All @@ -169,10 +159,7 @@ b = WithCappedDuration(2 * time.Second, b)
For a best-effort limit on the total execution time, specify a max duration:

```golang
b, err := NewFibonacci(1 * time.Second)
if err != nil {
// handle err
}
b := NewFibonacci(1 * time.Second)

// Ensure the maximum total retry time is 5s.
b = WithMaxDuration(5 * time.Second, b)
Expand Down
19 changes: 8 additions & 11 deletions backoff_constant.go
Expand Up @@ -2,27 +2,24 @@ package retry

import (
"context"
"fmt"
"time"
)

// Constant is a wrapper around Retry that uses a constant backoff.
// Constant is a wrapper around Retry that uses a constant backoff. It panics if
// the given base is less than zero.
func Constant(ctx context.Context, t time.Duration, f RetryFunc) error {
b, err := NewConstant(t)
if err != nil {
return err
}
return Do(ctx, b, f)
return Do(ctx, NewConstant(t), f)
}

// NewConstant creates a new constant backoff using the value t. The wait time
// is the provided constant value.
func NewConstant(t time.Duration) (Backoff, error) {
// is the provided constant value. It panics if the given base is less than
// zero.
func NewConstant(t time.Duration) Backoff {
if t <= 0 {
return nil, fmt.Errorf("t must be greater than 0")
panic("t must be greater than 0")
}

return BackoffFunc(func() (time.Duration, bool) {
return t, false
}), nil
})
}
22 changes: 5 additions & 17 deletions backoff_constant_test.go
@@ -1,11 +1,13 @@
package retry
package retry_test

import (
"fmt"
"reflect"
"sort"
"testing"
"time"

"github.com/sethvargo/go-retry"
)

func TestConstantBackoff(t *testing.T) {
Expand All @@ -16,12 +18,7 @@ func TestConstantBackoff(t *testing.T) {
base time.Duration
tries int
exp []time.Duration
err bool
}{
{
name: "zero",
err: true,
},
{
name: "single",
base: 1 * time.Nanosecond,
Expand Down Expand Up @@ -71,13 +68,7 @@ func TestConstantBackoff(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

b, err := NewConstant(tc.base)
if (err != nil) != tc.err {
t.Fatal(err)
}
if b == nil {
return
}
b := retry.NewConstant(tc.base)

resultsCh := make(chan time.Duration, tc.tries)
for i := 0; i < tc.tries; i++ {
Expand Down Expand Up @@ -108,10 +99,7 @@ func TestConstantBackoff(t *testing.T) {
}

func ExampleNewConstant() {
b, err := NewConstant(1 * time.Second)
if err != nil {
// handle err
}
b := retry.NewConstant(1 * time.Second)

for i := 0; i < 5; i++ {
val, _ := b.Next()
Expand Down
17 changes: 6 additions & 11 deletions backoff_exponential.go
Expand Up @@ -2,7 +2,6 @@ package retry

import (
"context"
"fmt"
"sync/atomic"
"time"
)
Expand All @@ -14,27 +13,23 @@ type exponentialBackoff struct {

// Exponential is a wrapper around Retry that uses an exponential backoff. It's
// very efficient, but does not check for overflow, so ensure you bound the
// retry.
// retry. It panics if the given base is less than zero.
func Exponential(ctx context.Context, base time.Duration, f RetryFunc) error {
b, err := NewExponential(base)
if err != nil {
return err
}
return Do(ctx, b, f)
return Do(ctx, NewExponential(base), f)
}

// NewExponential creates a new exponential backoff using the starting value of
// base and doubling on each failure (1, 2, 4, 8, 16, 32, 64...), up to max.
// It's very efficient, but does not check for overflow, so ensure you bound the
// retry.
func NewExponential(base time.Duration) (Backoff, error) {
// retry. It panics if the given base is less than 0.
func NewExponential(base time.Duration) Backoff {
if base <= 0 {
return nil, fmt.Errorf("base must be greater than 0")
panic("base must be greater than 0")
}

return &exponentialBackoff{
base: base,
}, nil
}
}

// Next implements Backoff. It is safe for concurrent use.
Expand Down
22 changes: 5 additions & 17 deletions backoff_exponential_test.go
@@ -1,11 +1,13 @@
package retry
package retry_test

import (
"fmt"
"reflect"
"sort"
"testing"
"time"

"github.com/sethvargo/go-retry"
)

func TestExponentialBackoff(t *testing.T) {
Expand All @@ -16,12 +18,7 @@ func TestExponentialBackoff(t *testing.T) {
base time.Duration
tries int
exp []time.Duration
err bool
}{
{
name: "zero",
err: true,
},
{
name: "single",
base: 1 * time.Nanosecond,
Expand Down Expand Up @@ -59,13 +56,7 @@ func TestExponentialBackoff(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

b, err := NewExponential(tc.base)
if (err != nil) != tc.err {
t.Fatal(err)
}
if b == nil {
return
}
b := retry.NewExponential(tc.base)

resultsCh := make(chan time.Duration, tc.tries)
for i := 0; i < tc.tries; i++ {
Expand Down Expand Up @@ -96,10 +87,7 @@ func TestExponentialBackoff(t *testing.T) {
}

func ExampleNewExponential() {
b, err := NewExponential(1 * time.Second)
if err != nil {
// handle err
}
b := retry.NewExponential(1 * time.Second)

for i := 0; i < 5; i++ {
val, _ := b.Next()
Expand Down
19 changes: 8 additions & 11 deletions backoff_fibonacci.go
Expand Up @@ -2,7 +2,6 @@ package retry

import (
"context"
"fmt"
"sync/atomic"
"time"
"unsafe"
Expand All @@ -14,26 +13,24 @@ type fibonacciBackoff struct {
state unsafe.Pointer
}

// Fibonacci is a wrapper around Retry that uses a Fibonacci backoff.
// Fibonacci is a wrapper around Retry that uses a Fibonacci backoff. It panics
// if the given base is less than zero.
func Fibonacci(ctx context.Context, base time.Duration, f RetryFunc) error {
b, err := NewFibonacci(base)
if err != nil {
return err
}
return Do(ctx, b, f)
return Do(ctx, NewFibonacci(base), f)
}

// NewFibonacci creates a new Fibonacci backoff using the starting value of
// base. The wait time is the sum of the previous two wait times on each failed
// attempt (1, 1, 2, 3, 5, 8, 13...).
func NewFibonacci(base time.Duration) (Backoff, error) {
// attempt (1, 1, 2, 3, 5, 8, 13...). It panics if the given base is less than
// zero.
func NewFibonacci(base time.Duration) Backoff {
if base <= 0 {
return nil, fmt.Errorf("base must be greater than 0")
panic("base must be greater than 0")
}

return &fibonacciBackoff{
state: unsafe.Pointer(&state{0, base}),
}, nil
}
}

// Next implements Backoff. It is safe for concurrent use.
Expand Down
22 changes: 5 additions & 17 deletions backoff_fibonacci_test.go
@@ -1,11 +1,13 @@
package retry
package retry_test

import (
"fmt"
"reflect"
"sort"
"testing"
"time"

"github.com/sethvargo/go-retry"
)

func TestFibonacciBackoff(t *testing.T) {
Expand All @@ -16,12 +18,7 @@ func TestFibonacciBackoff(t *testing.T) {
base time.Duration
tries int
exp []time.Duration
err bool
}{
{
name: "zero",
err: true,
},
{
name: "single",
base: 1 * time.Nanosecond,
Expand Down Expand Up @@ -71,13 +68,7 @@ func TestFibonacciBackoff(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

b, err := NewFibonacci(tc.base)
if (err != nil) != tc.err {
t.Fatal(err)
}
if b == nil {
return
}
b := retry.NewFibonacci(tc.base)

resultsCh := make(chan time.Duration, tc.tries)
for i := 0; i < tc.tries; i++ {
Expand Down Expand Up @@ -108,10 +99,7 @@ func TestFibonacciBackoff(t *testing.T) {
}

func ExampleNewFibonacci() {
b, err := NewFibonacci(1 * time.Second)
if err != nil {
// handle err
}
b := retry.NewFibonacci(1 * time.Second)

for i := 0; i < 5; i++ {
val, _ := b.Next()
Expand Down

0 comments on commit e436cb1

Please sign in to comment.