retry

package
v1.0.7 Latest Latest
Warning

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

Go to latest
Published: Oct 23, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package retry provides exponential backoff retry mechanisms for transient failures.

This package wraps github.com/cenkalti/backoff/v5 with application-specific retry patterns and error handling semantics. It offers several retry strategies:

  • Execute: Retries an operation until it succeeds or times out
  • ExecuteWithResult: Generic version that returns a result on success
  • ExecuteSQL: Specialized retry for SQL operations
  • WaitForCondition: Polls a condition until it becomes true
  • Sustain: Keeps a long-running operation alive, restarting on transient failures

The package defines special error types to control retry behavior:

  • ErrNonRetryable: Wraps permanent failures that should stop retries immediately
  • ErrBackOff: Indicates transient errors that should trigger exponential backoff

Retry behavior is configured through the Profile struct, which defines:

  • InitialInterval: Starting wait time between retries
  • RandomizationFactor: Jitter to prevent thundering herd
  • Multiplier: Factor by which wait time increases each retry
  • MaxInterval: Maximum wait time between retries
  • MaxElapsedTime: Total time budget for all retries (nil = default, 0 = unlimited)

Example Usage:

// Simple retry with default profile
err := retry.Execute(ctx, nil, func() error {
    return someOperation()
})

// Retry with custom profile and result
profile := &retry.Profile{
    InitialInterval: 100 * time.Millisecond,
    MaxElapsedTime:  30 * time.Second,
}
result, err := retry.ExecuteWithResult(ctx, profile, func() (*Data, error) {
    return fetchData()
})

// Keep a service running with automatic restart
err := retry.Sustain(ctx, profile, func() error {
    return runService() // Returns ErrBackOff on transient errors
})

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNonRetryable represents an error that should not trigger a retry.
	// It's used to wrap an underlying error condition when an operation
	// fails and retrying would be useless.
	// The code performing retries will check for this error type
	// (e.g., using errors.Is) and stop the retry loop if found.
	ErrNonRetryable = errors.New("cannot recover from error")
	// ErrBackOff is returned for transient errors specifically on an existing connection,
	// implying the initial connection was successful. Retry the operation after a backoff delay.
	ErrBackOff = errors.New("backoff required before retrying")
)

Functions

func Execute

func Execute(ctx context.Context, p *Profile, o func() error, terminalErrors ...error) error

Execute executes the given operation repeatedly until it succeeds or a timeout occurs. Errors matching terminalErrors stop retries immediately. It returns nil on success, or the error returned by the final attempt on timeout.

func ExecuteSQL

func ExecuteSQL(ctx context.Context, p *Profile, e executor, sqlStmt string, args ...any) error

ExecuteSQL executes the given SQL statement until it succeeds or a timeout occurs.

func ExecuteWithResult

func ExecuteWithResult[T any](
	ctx context.Context, p *Profile, operation func() (T, error), terminalErrors ...error,
) (T, error)

ExecuteWithResult executes the given operation repeatedly until it succeeds or a timeout occurs. Errors matching terminalErrors stop retries immediately.

func Sustain

func Sustain(ctx context.Context, p *Profile, op func() error) error

Sustain attempts to keep a continuous operation `op` running indefinitely. Unlike Execute which retries until success, Sustain is designed for long-running operations that should continue running until explicitly stopped or a permanent failure occurs.

Operation Behavior:

  • If op returns nil: Operation is running smoothly, Sustain resets backoff and retries immediately
  • If op returns ErrBackOff: Transient error, Sustain applies exponential backoff before retrying
  • If op returns ErrNonRetryable: Permanent failure, Sustain stops and returns the error
  • If op returns any other error: Retries immediately (treated as transient)

Sustain stops and returns when:

  • op returns an error wrapping ErrNonRetryable (permanent failure),
  • The context ctx is cancelled (returns context error),
  • The backoff strategy times out after MaxElapsedTime of continuous ErrBackOff errors.

func WaitForCondition

func WaitForCondition(ctx context.Context, p *Profile, condition func() bool) bool

WaitForCondition repeatedly evaluates the given condition function until it returns true or a timeout occurs. It uses exponential backoff between attempts as defined by the retry profile. Returns nil when the condition is satisfied, or an error if the context is cancelled or max elapsed time is reached.

Types

type Profile

type Profile struct {
	InitialInterval     time.Duration  `mapstructure:"initial-interval"`
	RandomizationFactor float64        `mapstructure:"randomization-factor"`
	Multiplier          float64        `mapstructure:"multiplier"`
	MaxInterval         time.Duration  `mapstructure:"max-interval"`
	MaxElapsedTime      *time.Duration `mapstructure:"max-elapsed-time" validate:"omitempty,gte=0"`
}

Profile can be used to define the backoff properties for retries.

MaxElapsedTime controls the total time budget for retries:

  • nil (unset): the default (15 minutes) is used.
  • 0: retries never stop (unlimited); the backoff runs until the operation succeeds, the context is cancelled, or a permanent error occurs.
  • positive: the backoff returns the underlying error after this duration.

Note: the unlimited (0) budget only removes the time limit from the backoff. gRPC connection retries remain bounded (see connection.MakeGrpcRetryPolicyJSON); unbounded service reconnection is provided by Sustain.

This is used as a workaround for known issues:

func (*Profile) NewBackoff

func (p *Profile) NewBackoff() *backoff.ExponentialBackOff

NewBackoff creates a new backoff.ExponentialBackOff instance with this profile.

Note: MaxElapsedTime is not set on the backoff instance itself. In backoff/v5, MaxElapsedTime is passed separately to backoff.Retry() as a configuration option via backoff.WithMaxElapsedTime(). This allows the same backoff instance to be reused with different timeout policies.

func (*Profile) WithDefaults

func (p *Profile) WithDefaults() *Profile

WithDefaults returns a clone of this profile with default values.

A nil MaxElapsedTime is replaced with the default (15 minutes). A non-nil value is preserved as-is, including an explicit 0 which requests unlimited retries. This makes WithDefaults idempotent for MaxElapsedTime.

Jump to

Keyboard shortcuts

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