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 ¶
- func Constant(ctx context.Context, t time.Duration, f RetryFunc) error
- func Do(ctx context.Context, b Backoff, f RetryFunc) error
- func DoValue[T any](ctx context.Context, b Backoff, f RetryFuncValue[T]) (T, error)
- func Exponential(ctx context.Context, base time.Duration, f RetryFunc) error
- func Fibonacci(ctx context.Context, base time.Duration, f RetryFunc) error
- func RetryableError(err error) error
- type Backoff
- func NewConstant(t time.Duration) Backoff
- func NewExponential(base time.Duration) Backoff
- func NewFibonacci(base time.Duration) Backoff
- func WithCappedDuration(cap time.Duration, next Backoff) Backoff
- func WithJitter(j time.Duration, next Backoff) Backoff
- func WithJitterPercent(j uint64, next Backoff) Backoff
- func WithMaxDuration(timeout time.Duration, next Backoff) Backoff
- func WithMaxRetries(max uint64, next Backoff) Backoff
- type BackoffFunc
- type RetryFunc
- type RetryFuncValue
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Constant ¶
Constant is a wrapper around Retry that uses a constant backoff. It panics if the given base is less than zero.
func Do ¶
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 := retry.NewFibonacci(1 * time.Nanosecond) // This example demonstrates selectively retrying specific errors. Only errors // wrapped with RetryableError are eligible to be retried. if err := retry.Do(ctx, retry.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 retry.RetryableError(fmt.Errorf("bad response: %v", resp.StatusCode)) default: return nil } }); err != nil { // handle error }
Output:
Example (Simple) ¶
ctx := context.Background() b := retry.NewFibonacci(1 * time.Nanosecond) i := 0 if err := retry.Do(ctx, retry.WithMaxRetries(3, b), func(ctx context.Context) error { fmt.Printf("%d\n", i) i++ return retry.RetryableError(fmt.Errorf("oops")) }); err != nil { // handle error }
Output: 0 1 2 3
func DoValue ¶ added in v0.3.0
Example ¶
ctx := context.Background() b := retry.NewFibonacci(1 * time.Nanosecond) body, err := retry.DoValue(ctx, retry.WithMaxRetries(3, b), func(ctx context.Context) ([]byte, error) { resp, err := http.Get("https://google.com/") if err != nil { return nil, err } defer resp.Body.Close() switch resp.StatusCode / 100 { case 4: return nil, fmt.Errorf("bad response: %v", resp.StatusCode) case 5: return nil, retry.RetryableError(fmt.Errorf("bad response: %v", resp.StatusCode)) default: b, _ := io.ReadAll(resp.Body) return b, nil } }) if err != nil { // handle error } _ = body
Output:
func Exponential ¶
Exponential is a wrapper around Retry that uses an exponential backoff. See NewExponential.
func Fibonacci ¶
Fibonacci is a wrapper around Retry that uses a Fibonacci backoff. See NewFibonacci.
func RetryableError ¶
RetryableError marks an error as retryable.
Types ¶
type Backoff ¶
type Backoff interface { // Next returns the time duration to wait and whether to stop. Next() (next time.Duration, stop bool) }
Backoff is an interface that backs off.
func NewConstant ¶
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 := retry.NewConstant(1 * time.Second) for i := 0; i < 5; i++ { val, _ := b.Next() fmt.Printf("%v\n", val) }
Output: 1s 1s 1s 1s 1s
func NewExponential ¶
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 := retry.NewExponential(1 * time.Second) for i := 0; i < 5; i++ { val, _ := b.Next() fmt.Printf("%v\n", val) }
Output: 1s 2s 4s 8s 16s
func NewFibonacci ¶
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 := retry.NewFibonacci(1 * time.Second) for i := 0; i < 5; i++ { val, _ := b.Next() fmt.Printf("%v\n", val) }
Output: 1s 2s 3s 5s 8s
func WithCappedDuration ¶
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 := retry.NewFibonacci(1 * time.Second) b = retry.WithCappedDuration(3*time.Second, b) if err := retry.Do(ctx, b, func(_ context.Context) error { // TODO: logic here return nil }); err != nil { // handle error }
Output:
func WithJitter ¶
WithJitter wraps a backoff function and adds the specified jitter. j can be interpreted as "+/- j". For example, if j were 5 seconds and the backoff returned 20s, the value could be between 15 and 25 seconds. The value can never be less than 0.
Example ¶
ctx := context.Background() b := retry.NewFibonacci(1 * time.Second) b = retry.WithJitter(1*time.Second, b) if err := retry.Do(ctx, b, func(_ context.Context) error { // TODO: logic here return nil }); err != nil { // handle error }
Output:
func WithJitterPercent ¶
WithJitterPercent wraps a backoff function and adds the specified jitter percentage. j can be interpreted as "+/- j%". For example, if j were 5 and the backoff returned 20s, the value could be between 19 and 21 seconds. The value can never be less than 0 or greater than 100.
Example ¶
ctx := context.Background() b := retry.NewFibonacci(1 * time.Second) b = retry.WithJitterPercent(5, b) if err := retry.Do(ctx, b, func(_ context.Context) error { // TODO: logic here return nil }); err != nil { // handle error }
Output:
func WithMaxDuration ¶
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 := retry.NewFibonacci(1 * time.Second) b = retry.WithMaxDuration(5*time.Second, b) if err := retry.Do(ctx, b, func(_ context.Context) error { // TODO: logic here return nil }); err != nil { // handle error }
Output:
func WithMaxRetries ¶
WithMaxRetries executes the backoff function up until the maximum attempts.
Example ¶
ctx := context.Background() b := retry.NewFibonacci(1 * time.Second) b = retry.WithMaxRetries(3, b) if err := retry.Do(ctx, b, func(_ context.Context) error { // TODO: logic here return nil }); err != nil { // handle error }
Output:
type BackoffFunc ¶
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 retry.Backoff) retry.BackoffFunc { return func() (time.Duration, bool) { val, stop := next.Next() if stop { return 0, true } return val + t, false } } // Middlewrap wrap another backoff: b := retry.NewFibonacci(1 * time.Second) b = withShift(5*time.Second, b) if err := retry.Do(ctx, b, func(ctx context.Context) error { // Actual retry logic here return nil }); err != nil { // handle error }
Output: