retry

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 10 Imported by: 0

README

retry

CI CodeQL Coverage Mutation Documentation Go Reference Release Go License

retry is a dependency-light foundation for bounded retry execution and backoff. Every policy requires a finite attempt limit, an error classifier, timing dependencies, a backoff strategy, and an operation. The package never assumes the operation is idempotent or safe to repeat.

policy, err := retry.NewPolicy(retry.Config{
    Backoff: retry.FullJitter(retry.Exponential(100*time.Millisecond, 2)),
    MaxAttempts: 4,
    MaxElapsed: 3*time.Second,
    MaxDelay: time.Second,
    Clock: retry.SystemClock{},
    Sleeper: retry.SystemSleeper{},
    Random: retry.NewRandom(1, 2),
    Classifier: retry.RetryableClassifier(),
})
if err != nil {
    return err
}

value, result, err := retry.Do(ctx, policy, func(ctx context.Context) (string, error) {
    value, err := readOnce(ctx)
    if isTransient(err) {
        return "", retry.Retryable(err)
    }
    return value, retry.Permanent(err)
})

The caller must decide whether readOnce is safe to repeat. Marking an error retryable classifies a failure; it does not make a side effect idempotent.

Features

  • Constant, linear, polynomial, Fibonacci, exponential, full-jitter, equal-jitter, exponential-jitter, and decorrelated-jitter backoff.
  • Maximum attempts plus elapsed, attempt, delay, and total-sleep budgets.
  • Injected clock, sleeper, random source, classifier, and observer.
  • Typed permanent, retryable, exhausted, canceled, and budget errors.
  • Generic value-returning execution with bounded result history.
  • HTTP Retry-After, pgx SQLSTATE, domain-predicate, slog, and OpenTelemetry adapters.
  • Deterministic vectors, statistical tests, fuzzing, race/leak checks, mutation checks, and comparative allocation benchmarks.

Documentation

Boundaries

This module owns no circuit state, rate limits, queues, schedules, idempotency keys, global policy, global random source, metrics registry, or background worker. Operation panics propagate and are never retried.

License

MIT. See LICENSE.

Ecosystem

Use the Golib documentation portal to choose companion packages, supported stacks, recipes, and operations guidance.

Documentation

Overview

Package retry executes explicitly classified operations under bounded retry policies. It never decides whether an operation is safe to repeat.

Index

Examples

Constants

View Source
const MaxHistoryEntries = 1024

MaxHistoryEntries is the largest failure history retained by a policy.

Variables

View Source
var ErrInvalidPolicy = errors.New("invalid retry policy")

ErrInvalidPolicy identifies contradictory, implicit, or unbounded policies.

Functions

func Permanent

func Permanent(err error) error

Permanent marks err as ineligible for retry.

func Retryable

func Retryable(err error) error

Retryable marks err as eligible for an explicitly configured retry policy.

Types

type Attempt

type Attempt struct {
	Attempt        uint
	Elapsed        time.Duration
	Delay          time.Duration
	Classification Classification
	Err            error
}

Attempt records bounded failure metadata. It never retains operation values.

type Backoff

type Backoff interface {
	Delay(attempt uint, previous time.Duration, random Random) time.Duration
}

Backoff computes the delay before retry attempt. Attempt starts at one for the first retry. Previous is the previously selected delay and is used only by state-dependent strategies such as decorrelated jitter.

func Constant

func Constant(delay time.Duration) Backoff

Constant returns the same non-negative delay for every attempt.

func DecorrelatedJitter

func DecorrelatedJitter(base time.Duration) Backoff

DecorrelatedJitter chooses uniformly between base and three times the previous delay. The first retry uses base as its previous delay.

func EqualJitter

func EqualJitter(backoff Backoff) Backoff

EqualJitter retains half of the wrapped delay and uniformly jitters the remainder.

func Exponential

func Exponential(initial time.Duration, multiplier uint64) Backoff

Exponential returns initial*multiplier^(attempt-1) with saturation.

func ExponentialJitter

func ExponentialJitter(initial time.Duration, multiplier uint64, factor float64) Backoff

ExponentialJitter applies centered proportional jitter to exponential backoff. Factor is clamped to [0, 1].

func Fibonacci

func Fibonacci(unit time.Duration) Backoff

Fibonacci returns unit multiplied by the attempt-th Fibonacci number, where the first two retry delays both equal unit.

func FullJitter

func FullJitter(backoff Backoff) Backoff

FullJitter chooses uniformly between zero and the wrapped delay.

Example
package main

import (
	"fmt"
	"time"

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

func main() {
	strategy := retry.FullJitter(retry.Exponential(100*time.Millisecond, 2))
	delay := strategy.Delay(1, 0, retry.NewRandom(1, 2))
	fmt.Println(delay >= 0 && delay <= 100*time.Millisecond)
}
Output:
true

func Linear

func Linear(initial, increment time.Duration) Backoff

Linear returns initial + (attempt-1)*increment, using saturating arithmetic.

func Polynomial

func Polynomial(base, coefficient time.Duration, power uint) Backoff

Polynomial returns base + coefficient*attempt^power with saturation.

type BudgetError

type BudgetError struct {
	Kind BudgetKind
	// contains filtered or unexported fields
}

BudgetError reports exhaustion of an elapsed, sleep, attempt, or shared-work budget.

func (*BudgetError) Error

func (err *BudgetError) Error() string

func (*BudgetError) Result

func (err *BudgetError) Result() Result

Result returns a defensive copy of terminal metadata.

func (*BudgetError) Unwrap

func (err *BudgetError) Unwrap() error

type BudgetKind

type BudgetKind string

BudgetKind identifies the configured budget that stopped execution.

const (
	// BudgetElapsed identifies the total elapsed-time budget.
	BudgetElapsed BudgetKind = "elapsed"
	// BudgetSleep identifies the accumulated-sleep budget.
	BudgetSleep BudgetKind = "sleep"
	// BudgetAttempt identifies a per-attempt timeout.
	BudgetAttempt BudgetKind = "attempt"
	// BudgetWork identifies denial by the shared retry-plus-hedge work budget.
	BudgetWork BudgetKind = "work"
)

type CanceledError

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

CanceledError reports cancellation by the caller or its deadline.

func (*CanceledError) Error

func (err *CanceledError) Error() string

func (*CanceledError) Result

func (err *CanceledError) Result() Result

Result returns a defensive copy of terminal metadata.

func (*CanceledError) Unwrap

func (err *CanceledError) Unwrap() error

type Classification

type Classification uint8

Classification is an explicit decision about one operation failure.

const (
	// ClassificationPermanent stops execution and preserves the operation error.
	ClassificationPermanent Classification = iota + 1
	// ClassificationRetryable permits another bounded attempt.
	ClassificationRetryable
)

type Classifier

type Classifier interface {
	Classify(context.Context, error) (Classification, error)
}

Classifier classifies an operation failure. Returning an error means the classifier itself failed and execution stops.

func RetryableClassifier

func RetryableClassifier() Classifier

RetryableClassifier returns a classifier that retries RetryableError values and treats every other error as permanent.

type ClassifyFunc

type ClassifyFunc func(context.Context, error) (Classification, error)

ClassifyFunc adapts a function to Classifier.

func (ClassifyFunc) Classify

func (function ClassifyFunc) Classify(ctx context.Context, err error) (Classification, error)

Classify invokes the adapted function.

type Clock

type Clock interface {
	Now() time.Time
}

Clock supplies policy time. Implementations may additionally implement TimeoutClock to make per-attempt timeouts deterministic.

type Config

type Config struct {
	Backoff        Backoff
	MaxAttempts    uint
	MaxElapsed     time.Duration
	AttemptTimeout time.Duration
	MinDelay       time.Duration
	MaxDelay       time.Duration
	MaxSleep       time.Duration
	Clock          Clock
	Sleeper        Sleeper
	Random         Random
	Classifier     Classifier
	Observer       Observer
	HistoryLimit   uint
	// UseResilienceBudget consumes the shared work-amplification scope attached
	// to the execution context. Existing standalone behavior is unchanged when false.
	UseResilienceBudget bool
}

Config contains all dependencies and bounds for a Policy. MaxAttempts is mandatory so no policy can retry forever.

type DelayHint

type DelayHint interface {
	RetryDelay(time.Time) (time.Duration, bool)
}

DelayHint is implemented by classified errors that carry a server-provided minimum retry delay, such as HTTP Retry-After.

type ExhaustedError

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

ExhaustedError reports that MaxAttempts stopped a retryable operation.

func (*ExhaustedError) Error

func (err *ExhaustedError) Error() string

func (*ExhaustedError) Result

func (err *ExhaustedError) Result() Result

Result returns a defensive copy of terminal metadata.

func (*ExhaustedError) Unwrap

func (err *ExhaustedError) Unwrap() error

type Observation

type Observation struct {
	Attempt        uint
	Elapsed        time.Duration
	NextDelay      time.Duration
	Classification Classification
	Reason         Reason
}

Observation is a bounded notification for one completed attempt.

type ObserveFunc

type ObserveFunc func(Observation)

ObserveFunc adapts a function to Observer.

func (ObserveFunc) Observe

func (function ObserveFunc) Observe(observation Observation)

Observe invokes the adapted function.

type Observer

type Observer interface {
	Observe(Observation)
}

Observer receives bounded lifecycle metadata.

type PermanentError

type PermanentError struct{ Cause error }

PermanentError explicitly marks a cause as ineligible for retry.

func (*PermanentError) Error

func (err *PermanentError) Error() string

func (*PermanentError) Unwrap

func (err *PermanentError) Unwrap() error

type Policy

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

Policy is an immutable, explicitly bounded retry policy.

func NewPolicy

func NewPolicy(config Config) (*Policy, error)

NewPolicy validates and copies config.

type Random

type Random interface {
	Int64n(upper int64) int64
}

Random is an injected, concurrency-safe source of uniform integers in [0, upper). Implementations must return zero when upper is non-positive.

type Reason

type Reason string

Reason identifies why execution stopped.

const (
	// ReasonSucceeded identifies a successful operation.
	ReasonSucceeded Reason = "succeeded"
	// ReasonPermanent identifies an explicitly permanent failure.
	ReasonPermanent Reason = "permanent"
	// ReasonAttemptsExhausted identifies maximum-attempt exhaustion.
	ReasonAttemptsExhausted Reason = "attempts_exhausted"
	// ReasonCanceled identifies caller cancellation or deadline.
	ReasonCanceled Reason = "canceled"
	// ReasonElapsedBudget identifies total elapsed-time exhaustion.
	ReasonElapsedBudget Reason = "elapsed_budget"
	// ReasonSleepBudget identifies accumulated-sleep exhaustion.
	ReasonSleepBudget Reason = "sleep_budget"
	// ReasonAttemptBudget identifies a per-attempt timeout.
	ReasonAttemptBudget Reason = "attempt_budget"
	// ReasonClassifierFailure identifies a classifier error or invalid result.
	ReasonClassifierFailure Reason = "classifier_failure"
	// ReasonSleeperFailure identifies a non-context sleeper failure.
	ReasonSleeperFailure Reason = "sleeper_failure"
	// ReasonWorkBudget identifies local denial by the shared amplification budget.
	ReasonWorkBudget Reason = "work_budget"
)

type Result

type Result struct {
	Attempts   uint
	Elapsed    time.Duration
	FinalDelay time.Duration
	Reason     Reason
	History    []Attempt
}

Result contains bounded execution metadata and never retains operation values.

func Do

func Do[T any](ctx context.Context, policy *Policy, operation func(context.Context) (T, error)) (T, Result, error)

Do executes operation under policy. The caller remains solely responsible for deciding whether repeating operation is safe.

Example
package main

import (
	"context"
	"errors"
	"fmt"

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

func main() {
	policy, err := retry.NewPolicy(retry.Config{
		Backoff: retry.Constant(0), MaxAttempts: 3,
		Clock: retry.SystemClock{}, Sleeper: retry.SystemSleeper{},
		Classifier: retry.RetryableClassifier(), HistoryLimit: 2,
	})
	if err != nil {
		panic(err)
	}
	attempts := 0
	value, result, err := retry.Do(context.Background(), policy, func(context.Context) (string, error) {
		attempts++
		if attempts == 1 {
			return "", retry.Retryable(errors.New("temporary"))
		}
		return "ready", nil
	})
	fmt.Println(value, result.Attempts, err)
}
Output:
ready 2 <nil>

type RetryableError

type RetryableError struct{ Cause error }

RetryableError explicitly marks a cause as eligible for bounded retry.

func (*RetryableError) Error

func (err *RetryableError) Error() string

func (*RetryableError) Unwrap

func (err *RetryableError) Unwrap() error

type SeededRandom

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

SeededRandom is a deterministic, concurrency-safe PCG random source.

func NewRandom

func NewRandom(seed1, seed2 uint64) *SeededRandom

NewRandom constructs a deterministic random source from explicit seeds.

func (*SeededRandom) Int64n

func (random *SeededRandom) Int64n(upper int64) int64

Int64n returns a uniform value in [0, upper).

type Sleeper

type Sleeper interface {
	Sleep(context.Context, time.Duration) error
}

Sleeper waits without owning policy or retry decisions.

type SystemClock

type SystemClock struct{}

SystemClock uses the process monotonic wall clock. It contains no global mutable state.

func (SystemClock) Now

func (SystemClock) Now() time.Time

Now returns the current time.

func (SystemClock) WithTimeout

func (SystemClock) WithTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc)

WithTimeout derives a standard context timeout.

type SystemSleeper

type SystemSleeper struct{}

SystemSleeper waits with a context-owned timer and always stops the timer.

func (SystemSleeper) Sleep

func (SystemSleeper) Sleep(ctx context.Context, delay time.Duration) error

Sleep waits for delay or context cancellation.

type TimeoutClock

type TimeoutClock interface {
	Clock
	WithTimeout(context.Context, time.Duration) (context.Context, context.CancelFunc)
}

TimeoutClock derives deadline contexts using an injected clock.

Directories

Path Synopsis
Package retryadapter provides explicit classifier seams for integrations whose transient failures are domain-specific.
Package retryadapter provides explicit classifier seams for integrations whose transient failures are domain-specific.
Package retryhttp classifies HTTP response failures and parses Retry-After.
Package retryhttp classifies HTTP response failures and parses Retry-After.
Package retrylog adapts bounded retry observations to log/slog, the logging API used by log.
Package retrylog adapts bounded retry observations to log/slog, the logging API used by log.
Package retrypgx classifies PostgreSQL errors by SQLSTATE.
Package retrypgx classifies PostgreSQL errors by SQLSTATE.
Package retrytelemetry adapts bounded retry observations to the standard OpenTelemetry API accepted by telemetry.
Package retrytelemetry adapts bounded retry observations to the standard OpenTelemetry API accepted by telemetry.

Jump to

Keyboard shortcuts

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