retry

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Feb 7, 2026 License: MIT Imports: 6 Imported by: 0

README

retry

Tests

A small Go package for executing operations with retries.

The package provides:

  • Pluggable backoff strategies (fixed, linear, exponential)
  • Context-aware cancellation and timeouts
  • Retry limits (including infinite retries)
  • Explicit support for non-retryable errors

Installation

go get github.com/er-davo/retry

Quick start

err := retry.Do(ctx, 3, func(attempt int) error {
    return callExternalService()
})
  • attempt is zero-based
  • returning nil stops retries immediately
  • returning an error triggers retry logic

Retrier

For more control, use the configurable Retrier:

r := retry.New(
    retry.WithMaxAttempts(5),
    retry.WithBackoff(retry.ExponentialBackoff{
        Base:   time.Second,
        Factor: 2,
        Max:    30 * time.Second,
        Jitter: 0.2,
    }),
)

err := r.Do(ctx, func(attempt int) error {
    return doWork()
})

Backoff strategies

Fixed backoff
retry.FixedBackoff{
    Interval: time.Second,
    Jitter:   0.1,
}
Linear backoff
retry.LinearBackoff{
    Base:   time.Second,
    Step:   time.Second,
    Max:    10 * time.Second,
    Jitter: 0.1,
}
Exponential backoff
retry.ExponentialBackoff{
    Base:   time.Second,
    Factor: 2,
    Max:    30 * time.Second,
    Jitter: 0.2,
}

All backoff strategies support optional jitter to reduce coordinated retries (thundering herd problem).


Retry limits

retry.WithMaxAttempts(3)
  • maxAttempts > 0 — retry up to the specified number of attempts
  • maxAttempts == 0 — retry indefinitely until the context is canceled

Non-retryable errors

Retryability is controlled by a user-provided function, not by returning a special error from the attempt itself.

When creating a Retrier, you can provide an IsRetryableFunc:

r := retry.New(
    retry.WithIsRetryableFunc(func(err error) bool {
        // return false for errors that should NOT be retried
        return !errors.Is(err, ErrPermanent)
    }),
)

If an attempt returns an error and IsRetryableFunc returns false, the retry loop stops immediately.

In this case, the retrier wraps the original error into an UnretryableError:

return newUnretryableError(err)

This design allows callers to distinguish why the retry stopped:

  • the operation succeeded
  • retries were exhausted
  • the context was canceled
  • a non-retryable error was encountered

The original error is preserved and can be inspected using errors.Unwrap or errors.As:

var ure *retry.UnretryableError
if errors.As(err, &ure) {
    // retry stopped because the error was marked as non-retryable
}

Context handling

The retry loop respects context.Context:

  • retries stop immediately when the context is canceled
  • backoff waiting is interrupted on cancellation

This makes the package safe to use in:

  • HTTP handlers
  • gRPC requests
  • background workers

Design notes

  • Retrier instances are not thread-safe and should not be reused concurrently
  • Backoff strategies are fully decoupled from retry logic

License

MIT

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Do

func Do(ctx context.Context, maxAttempts int, f AttemptFunc) error

Do executes the provided function with retry semantics.

Parameters:

  • ctx controls cancellation and timeouts.
  • maxAttempts defines the maximum number of attempts. A value of 0 means retry indefinitely until the context is canceled.
  • f is the function to execute; it receives the zero-based attempt number.

This is a convenience wrapper around New(...) with default configuration and a custom maxAttempts value.

func IsUnretryable

func IsUnretryable(err error) bool

IsUnretryable reports whether the error is marked as unretryable.

Types

type AttemptFunc

type AttemptFunc func(int) error

AttemptFunc represents a single retryable operation. The argument is the zero-based attempt number. Returning nil indicates success; a non-nil error triggers retry logic.

type Backoff

type Backoff interface {
	// Next returns the duration to wait before the next retry attempt.
	// The attempt parameter is zero-based (first retry = attempt 0).
	Next(attempt int) time.Duration
}

Backoff defines a strategy for calculating delay durations between retry attempts.

type ExponentialBackoff

type ExponentialBackoff struct {
	Base   time.Duration
	Factor float64
	Max    time.Duration
	Jitter float64
}

ExponentialBackoff increases the delay exponentially with each attempt.

Base is the initial delay. Factor is the exponential multiplier (e.g. 2.0). Max caps the maximum delay (0 means no limit). Jitter adds a random variation as a fraction of the computed delay.

func (ExponentialBackoff) Next

func (e ExponentialBackoff) Next(attempt int) time.Duration

Next returns an exponentially increasing delay with optional max cap and jitter.

type FixedBackoff

type FixedBackoff struct {
	Interval time.Duration
	Jitter   float64
}

FixedBackoff implements a constant delay between attempts.

Interval defines the base delay duration. Jitter adds a random variation in the range [-Jitter, +Jitter] as a fraction of Interval (e.g. 0.2 = ±20%).

func (FixedBackoff) Next

func (f FixedBackoff) Next(attempt int) time.Duration

Next returns a constant delay with optional jitter applied.

type IsRetryableFunc

type IsRetryableFunc func(error) bool

IsRetryableFunc determines whether an error is retryable. Returning false stops retries immediately.

type LinearBackoff

type LinearBackoff struct {
	Base   time.Duration
	Step   time.Duration
	Max    time.Duration
	Jitter float64
}

LinearBackoff increases the delay linearly with each attempt.

Base is the initial delay. Step is added for each subsequent attempt. Max caps the maximum delay (0 means no limit). Jitter adds a random variation as a fraction of the computed delay.

func (LinearBackoff) Next

func (l LinearBackoff) Next(attempt int) time.Duration

Next returns a linearly increasing delay with optional max cap and jitter.

type Retrier

type Retrier interface {
	// Do executes the provided AttemptFunc until it succeeds,
	// the context is canceled, or retry limits are exceeded.
	Do(context.Context, AttemptFunc) error
}

Retrier executes an operation with retry semantics.

func New

func New(opts ...RetryOption) Retrier

New creates a new Retrier with optional configuration. By default, it uses:

  • a linear backoff
  • a maximum of 3 attempts
  • a retryable check that retries on any non-nil error

func NoRetry added in v1.0.1

func NoRetry() Retrier

NoRetry returns a Retrier that executes the operation once.

type RetryOption

type RetryOption func(*retrier)

RetryOption configures a Retrier.

func WithBackoff

func WithBackoff(backoff Backoff) RetryOption

WithBackoff sets a custom backoff strategy.

func WithIsRetryableFunc

func WithIsRetryableFunc(isRetryable IsRetryableFunc) RetryOption

WithIsRetryableFunc sets a custom function to determine whether an error should be retried.

func WithMaxAttempts

func WithMaxAttempts(maxAttempts int) RetryOption

WithMaxAttempts sets the maximum number of retry attempts. A value of 0 means unlimited retries.

type UnretryableError

type UnretryableError struct {
	// contains filtered or unexported fields
}

UnretryableError marks an error as non-retryable.

When this error is returned (or wrapped), the retry mechanism should stop immediately and propagate the error to the caller. The original cause is preserved and can be accessed via errors.Unwrap or errors.As.

func (*UnretryableError) Error

func (e *UnretryableError) Error() string

func (*UnretryableError) Unwrap

func (e *UnretryableError) Unwrap() error

Jump to

Keyboard shortcuts

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