retry

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2022 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package retry provides helpers for retrying.

This package defines flexible interfaces for retrying Go functions that may be flakey or eventually consistent. It abstracts the "backoff" (how long to wait between tries) and "retry" (execute the function again) mechanisms for maximum flexibility. Furthermore, everything is an interface, so you can define your own implementations.

The package is modeled after Go's built-in HTTP package, making it easy to customize the built-in backoff with your own custom logic. Additionally, callers specify which errors are retryable by wrapping them. This is helpful with complex operations where only certain results should retry.

Index

Examples

Constants

View Source
const Stop = time.Duration(-1)

Stop value signals the backoff to stop retrying.

Variables

This section is empty.

Functions

func Constant

func Constant(ctx context.Context, t time.Duration, f RetryFunc) error

Constant is a wrapper around Retry that uses a constant backoff. It panics if the given base is less than zero.

func Do

func Do(ctx context.Context, b Backoff, f RetryFunc) error

Do wraps a function with a backoff to retry. The provided context is the same context passed to the RetryFunc.

Example (CustomRetry)
ctx := context.Background()

b := NewFibonacci(1 * time.Nanosecond)

// This example demonstrates selectively retrying specific errors. Only errors
// wrapped with RetryableError are eligible to be retried.
if err := Do(ctx, WithMaxRetries(3, b), func(ctx context.Context) error {
	resp, err := http.Get("https://google.com/")
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	switch resp.StatusCode / 100 {
	case 4:
		return fmt.Errorf("bad response: %v", resp.StatusCode)
	case 5:
		return RetryableError(fmt.Errorf("bad response: %v", resp.StatusCode))
	default:
		return nil
	}
}); err != nil {
	// handle error
}
Output:

Example (Simple)
ctx := context.Background()

b := NewFibonacci(1 * time.Nanosecond)

i := 0
if err := Do(ctx, WithMaxRetries(3, b), func(ctx context.Context) error {
	fmt.Printf("%d\n", i)
	i++
	return RetryableError(fmt.Errorf("oops"))
}); err != nil {
	// handle error
}
Output:

0
1
2
3

func Exponential

func Exponential(ctx context.Context, base time.Duration, f RetryFunc) error

Exponential is a wrapper around Retry that uses an exponential backoff. See NewExponential.

func Fibonacci

func Fibonacci(ctx context.Context, base time.Duration, f RetryFunc) error

Fibonacci is a wrapper around Retry that uses a Fibonacci backoff. See NewFibonacci.

func IsStopped

func IsStopped(delay time.Duration) bool

IsStopped reports whether the backoff shall stop.

func RetryableError

func RetryableError(err error) error

RetryableError marks an error as retryable.

Types

type Backoff

type Backoff interface {
	// Next takes the error and returns the time duration to wait and the
	// processed error. A duration less than zero signals the backoff to stop
	// and to not retry again.
	Next(err error) (time.Duration, error)
}

Backoff is an interface that backs off.

func NewConstant

func NewConstant(t time.Duration) Backoff

NewConstant creates a new constant backoff using the value t. The wait time is the provided constant value. It panics if the given base is less than zero.

Example
b := NewConstant(1 * time.Second)

for i := 0; i < 5; i++ {
	delay, _ := b.Next(nil)
	fmt.Printf("%v\n", delay)
}
Output:

1s
1s
1s
1s
1s

func NewExponential

func NewExponential(base time.Duration) Backoff

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.

Once it overflows, the function constantly returns the maximum time.Duration for a 64-bit integer.

It panics if the given base is less than zero.

Example
b := NewExponential(1 * time.Second)

for i := 0; i < 5; i++ {
	delay, _ := b.Next(nil)
	fmt.Printf("%v\n", delay)
}
Output:

1s
2s
4s
8s
16s

func NewFibonacci

func NewFibonacci(base time.Duration) Backoff

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...).

Once it overflows, the function constantly returns the maximum time.Duration for a 64-bit integer.

It panics if the given base is less than zero.

Example
b := NewFibonacci(1 * time.Second)

for i := 0; i < 5; i++ {
	delay, _ := b.Next(nil)
	fmt.Printf("%v\n", delay)
}
Output:

1s
2s
3s
5s
8s

func WithCappedDuration

func WithCappedDuration(cap time.Duration, next Backoff) Backoff

WithCappedDuration sets a maximum on the duration returned from the next backoff. This is NOT a total backoff time, but rather a cap on the maximum value a backoff can return. Without another middleware, the backoff will continue infinitely.

Example
ctx := context.Background()

b := NewFibonacci(1 * time.Second)
b = WithCappedDuration(3*time.Second, b)

if err := Do(ctx, b, func(_ context.Context) error {
	// TODO: logic here
	return nil
}); err != nil {
	// handle error
}
Output:

func WithJitter

func WithJitter(j time.Duration, addOnly bool, next Backoff) Backoff

WithJitter wraps a backoff function and adds the specified jitter. If addOnly is specified, then a jitter up to +j will be added on top of the backoff; otherwise a jitter up to ±j will be applied. For example, if j is 5s, addOnly is false and the backoff returned is 20s, then the resulting value could be between 15 and 25 seconds. Panics if j is less than 0.

Example
ctx := context.Background()

b := NewFibonacci(1 * time.Second)
b = WithJitter(1*time.Second, false, b)

if err := Do(ctx, b, func(_ context.Context) error {
	// TODO: logic here
	return nil
}); err != nil {
	// handle error
}
Output:

func WithJitterPercent

func WithJitterPercent(j uint64, addOnly bool, next Backoff) Backoff

WithJitterPercent wraps a backoff function and adds the specified jitter percentage. If addOnly is specified, then a jitter up to +j% will be added on top of the backoff; otherwise a jitter up to ±j% will be applied. For example, if j is 5, addOnly is false and the backoff returned is 20s, then the resulting value could be between 19 and 21 seconds. Panics if j is less than 0 or greater than 100.

Example
ctx := context.Background()

b := NewFibonacci(1 * time.Second)
b = WithJitterPercent(5, false, b)

if err := Do(ctx, b, func(_ context.Context) error {
	// TODO: logic here
	return nil
}); err != nil {
	// handle error
}
Output:

func WithMaxDuration

func WithMaxDuration(timeout time.Duration, next Backoff) Backoff

WithMaxDuration sets a maximum on the total amount of time a backoff should execute. It's best-effort, and should not be used to guarantee an exact amount of time.

Example
ctx := context.Background()

b := NewFibonacci(1 * time.Second)
b = WithMaxDuration(5*time.Second, b)

if err := Do(ctx, b, func(_ context.Context) error {
	// TODO: logic here
	return nil
}); err != nil {
	// handle error
}
Output:

func WithMaxRetries

func WithMaxRetries(max uint64, next Backoff) Backoff

WithMaxRetries executes the backoff function up until the maximum attempts.

Example
ctx := context.Background()

b := NewFibonacci(1 * time.Second)
b = WithMaxRetries(3, b)

if err := Do(ctx, b, func(_ context.Context) error {
	// TODO: logic here
	return nil
}); err != nil {
	// handle error
}
Output:

func WithRetryable

func WithRetryable(next Backoff) Backoff

WithRetryable wraps a backoff function and adds a check for a RetryableError. When a non RetryableError then no more retry is performed.

type BackoffFunc

type BackoffFunc func(err error) (time.Duration, error)

BackoffFunc is a backoff expressed as a function.

Example
ctx := context.Background()

// Example backoff middleware that adds the provided duration t to the result.
withShift := func(t time.Duration, next Backoff) BackoffFunc {
	return func(err error) (time.Duration, error) {
		delay, err := next.Next(err)
		if IsStopped(delay) {
			return Stop, err
		}
		return delay + t, err
	}
}

// Middlewrap wrap another backoff:
b := NewFibonacci(1 * time.Second)
b = withShift(5*time.Second, b)

if err := Do(ctx, b, func(ctx context.Context) error {
	// Actual retry logic here
	return nil
}); err != nil {
	// handle error
}
Output:

func (BackoffFunc) Next

func (b BackoffFunc) Next(err error) (time.Duration, error)

Next implements Backoff.

type RetryFunc

type RetryFunc func(ctx context.Context) error //revive:disable-line

RetryFunc is a function passed to retry.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL