retry

package
v0.28.0 Latest Latest
Warning

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

Go to latest
Published: Jul 2, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Overview

Package retry adds durability and tail-latency control to calls over unreliable transports (node-to-node RPCs, S3). It provides three things, all allocation-light and used only off the data plane's hot path:

  • bounded retries with exponential backoff + jitter (Do), for calls that are safe to repeat;
  • per-attempt timeouts, so a single slow attempt cannot stall a call for the whole deadline;
  • hedged (opportunistic concurrent) retries (Hedge) for idempotent calls: stagger a second attempt once the first is slow, race them, take the first success, and cancel the losers — the classic defense against a tail of slow/stuck requests in a lossy, noisy environment.

A Policy is a value; the zero value runs exactly one attempt with no timeout (i.e. a plain call), so wiring it in is always safe.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConnFailure

func ConnFailure(err error) bool

ConnFailure reports whether err means the request never reached the server — a dial failure or a refused connection. It is the *safe* retry predicate for non-idempotent calls (writes): retrying is correct only when we know the server did not act on the request. An ambiguous failure (a timeout after the body was sent) returns false, giving writes at-most-once semantics.

func Do

func Do[T any](ctx context.Context, p Policy, fn func(context.Context) (T, error)) (T, error)

Do runs fn with sequential retries under the policy: at most Policy.MaxAttempts attempts, each bounded by Policy.PerTryTimeout, spaced by exponential backoff. It stops early on success, on a non-retryable error, or when ctx is done. Use it for calls that are safe to repeat but should not run concurrently (e.g. a write with a bounded retry budget).

func Hedge

func Hedge[T any](ctx context.Context, p Policy, thunks []func(context.Context) (T, error)) (T, error)

Hedge issues the thunks as opportunistic concurrent attempts: it launches the first immediately, and launches each next one either after Policy.HedgeDelay elapses with no result (the in-flight attempt is slow) or as soon as an attempt fails retryably (failover). It returns the first success and cancels the rest; a non-retryable error short-circuits. All thunks must be idempotent — several may run at once. With one thunk it degrades to a single Policy.PerTryTimeout-bounded call.

func Repeat

func Repeat[T any](thunk func(context.Context) (T, error), n int) []func(context.Context) (T, error)

Repeat builds n identical thunks from one call, for hedging against a single endpoint (e.g. a slow S3 GET re-issued on a fresh connection): the second attempt is a retry, not a different host.

func RetryableStatus

func RetryableStatus(code int) bool

RetryableStatus reports whether an HTTP status code is a transient server-side failure (5xx, or 429 Too Many Requests). 4xx (except 429) is a permanent client error and must not be retried.

func Transient

func Transient(err error) bool

Transient reports whether err is worth retrying for an *idempotent* call: transport failures (dial/connection errors, resets, timeouts), a per-attempt deadline, and unexpected EOFs. A parent-context cancellation is never retried (the caller gave up). Unknown non-nil errors are treated as transient, since an idempotent call is safe to repeat — pair this only with calls that truly are idempotent.

Types

type Policy

type Policy struct {
	// MaxAttempts is the total number of attempts for one logical call (≥1). 0 means 1.
	MaxAttempts int

	// PerTryTimeout bounds a single attempt. 0 disables it (the attempt runs until the parent
	// context's deadline). With hedging, a slow attempt is bypassed by [HedgeDelay] regardless;
	// the per-try timeout additionally bounds the resources a stuck attempt holds.
	PerTryTimeout time.Duration

	// BaseBackoff is the wait before the second sequential attempt in [Do]; it doubles each attempt
	// up to MaxBackoff, with equal jitter. 0 disables backoff (attempts run back-to-back).
	BaseBackoff time.Duration
	MaxBackoff  time.Duration

	// HedgeDelay is how long [Hedge] waits for the in-flight attempt before launching the next one
	// concurrently. 0 makes [Hedge] a pure sequential failover (the next launches only when the
	// current one fails). Tune it above the normal-case latency so the fast path never hedges.
	HedgeDelay time.Duration

	// Retryable classifies an error: true ⇒ try the next attempt, false ⇒ fail immediately (e.g.
	// a "not found" or a 4xx is permanent). nil ⇒ retry every error except a parent-context
	// cancellation.
	Retryable func(error) bool

	// Rand returns a float in [0,1) for backoff jitter. nil ⇒ the default global source.
	Rand func() float64

	// Observability hooks (all optional). attempt is 0-based; attempt 0 is the first try.
	OnAttempt func(attempt int)                                // every launch (incl. the first)
	OnRetry   func(attempt int, err error, wait time.Duration) // before a sequential retry waits
	OnHedge   func(attempt int)                                // when a hedged attempt is launched (attempt ≥ 1)
}

Policy configures retries, per-attempt timeouts, and hedging. It is a plain value; copy freely.

Jump to

Keyboard shortcuts

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