Help us shape the future of Go! Take the 2026 Go Developer Survey

retry

package
v0.4.2 Latest Latest
Warning

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

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

Documentation

Overview

Package retry owns every deadline-bounded wait in the suites. Nothing sleeps; the framework polls. Hand-rolled loops are the main cause of flaky parallel tests, and the design this is ported from accumulated 31 copies of the same loop before consolidation — one of which had silently drifted onto a hardcoded window that ignored the shared ceiling and hid a CI failure.

One loop, three contracts. Picking a contract IS the statement of intent, the same way the tcontext read intents are:

Until          - the result IS the assertion target. Returns the LAST result so
                 the caller publishes it and asserts the exact value itself.
                 Side-effect free. Retries only transient connectivity, so a bad
                 context key or payload fails fast instead of being masked as a
                 timeout.
OrHeal         - a self-healing gate for PREREQUISITE state only, never an
                 assertion target. Waits the full propagation window, then
                 re-fires the triggering action, because runtime-propagation
                 events are at-most-once and a dropped one can only be fixed by
                 re-emitting it. Treats every error as not-ready — during warm-up
                 "not ready" and "probe failed" are indistinguishable — and fails
                 the test on exhaustion. Logs each heal so occurrences stay
                 countable.
SettledCount   - the assertion target is a monotonic counter's FINAL value. Polls
                 until the value has been unchanged for a quiet period, then the
                 caller asserts both settled and the exact count. Necessary
                 because Until can only ask "has it reached N", so accept-on-N
                 passes the instant the counter touches N and an extra arrival a
                 moment later is invisible — an over-count wearing an exact
                 assertion.

Using OrHeal for an assertion target swallows fail-fast errors. Using Until for prerequisite state never re-fires a dropped event. Using Until for a final count silently permits an over-count.

Every deadline is FLOORED at the single propagation ceiling, so no call site can drift below the shared value. An unexpected 401 inside a loop must be reported with the credential's identifier, not just retried: a rejected credential is revoked and can never recover by being replayed, and re-minting it would hide a product defect.

Index

Constants

View Source
const BaseInterval = 750 * time.Millisecond

BaseInterval is the poll cadence floor. A shorter Interval is raised to it, so a wait cannot hammer a component that is already behind.

View Source
const FastInterval = 100 * time.Millisecond

FastInterval is the poll cadence floor for a wait that asks for Options.Fast.

View Source
const PropagationCeiling = 60 * time.Second

PropagationCeiling is the deadline floor for every wait here. A shorter Timeout is raised to it, so no call site waits less than the shared minimum.

Variables

This section is empty.

Functions

func Await

func Await[T any](
	ctx context.Context, opts Options, attempt Attempt[T], accept Accept[T], what string,
) error

Await polls until accept holds, and fails when it never did.

Prefer this over Until. Until returns a NIL ERROR when every attempt succeeded at the transport level but none ever satisfied accept, so a caller that checks only the error passes after burning its whole budget. Await folds that check in, so it cannot be forgotten. Use Until only where the failure message needs the last result.

func IsTransient

func IsTransient(err error) bool

IsTransient reports whether an error was marked retryable.

func LogAuthRejection

func LogAuthRejection(log *slog.Logger, what, credentialKey string, status int, body string, elapsed time.Duration) string

LogAuthRejection logs an authentication rejection seen inside a wait and returns the same text for the caller's assertion message. A rejected credential will not start working, so the wait stops here rather than timing out.

func Never

func Never[T any](
	ctx context.Context, opts Options, window time.Duration, attempt Attempt[T], forbidden Forbidden[T],
) (T, error)

Never polls for the whole window and fails the moment forbidden holds, proving absence across time rather than at one instant.

The window must exceed the longest latency the positive path allows, or the check proves nothing. It is NOT floored at PropagationCeiling: for a negative the window is a cost-versus-confidence choice. Zero means PropagationCeiling.

Returns the last observed result either way.

func OrHeal

func OrHeal(ctx context.Context, opts HealOptions, probe Probe, trigger Trigger) error

OrHeal waits for prerequisite state, re-firing the trigger if it does not arrive, because propagation events are at-most-once. Every probe error counts as not-ready. Not side-effect free, so never use it for an assertion target.

func Transient

func Transient(err error) error

Transient marks an error as retryable — a connection refused while a component warms up, for instance. Anything not marked is treated as a programming error and fails fast.

func Until

func Until[T any](ctx context.Context, opts Options, attempt Attempt[T], accept Accept[T]) (T, error)

Until polls until accept is satisfied and returns the last result either way, so the caller asserts on the exact value. Side-effect free. Only transient attempt errors are retried.

Types

type Accept

type Accept[T any] func(T) bool

Accept reports whether a result is the one being waited for.

type Attempt

type Attempt[T any] func(ctx context.Context) (T, error)

Attempt produces a result. Its error means the call could not be made, not that it returned something unwanted.

type Counter

type Counter func(ctx context.Context) (int, error)

Counter reports a monotonically non-decreasing value.

type Forbidden

type Forbidden[T any] func(T) bool

Forbidden reports whether an attempt's result violates the invariant.

type HealOptions

type HealOptions struct {
	Options
	// What names the awaited state, for diagnostics.
	What string
	// MaxHeals bounds how many times the trigger is re-fired.
	MaxHeals int
	// HealAfter is how long to wait before re-firing.
	HealAfter time.Duration
}

HealOptions tune a self-healing gate.

type Options

type Options struct {
	// Timeout is a MINIMUM, floored at PropagationCeiling.
	Timeout time.Duration
	// Interval is the base polling cadence.
	Interval time.Duration
	// Fast lowers the cadence floor from BaseInterval to FastInterval. Only for waits whose
	// polls put no load on the component under test, such as log, listing and metrics reads.
	Fast bool
	// Retryable classifies an attempt error as transient. Nil means only errors matching
	// IsTransient are retried.
	Retryable func(error) bool
	// Logger receives self-heal and diagnostic lines.
	Logger *slog.Logger
	// contains filtered or unexported fields
}

Options tune a wait.

type Probe

type Probe func(ctx context.Context) (bool, error)

Probe reports whether the awaited state has arrived.

type Settled

type Settled struct {
	// Value is the last observed value.
	Value int
	// Quiet reports whether the value stopped changing for the required quiet period. When
	// false, the value was still moving when the deadline passed.
	Quiet bool
	// Samples is how many observations were taken.
	Samples int
}

Settled is the outcome of waiting for a counter to go quiet.

func SettledCount

func SettledCount(ctx context.Context, opts Options, quiet time.Duration, count Counter) (Settled, error)

SettledCount waits until a counter stops changing for quiet, then reports its final value. Use it when the assertion target is HOW MANY arrived: Until would return the moment the counter touches N and miss a later arrival. Choose quiet longer than the gap between arrivals.

type Trigger

type Trigger func(ctx context.Context) error

Trigger re-fires the action whose effect is being waited for.

Jump to

Keyboard shortcuts

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