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
- func Await[T any](ctx context.Context, opts Options, attempt Attempt[T], accept Accept[T], ...) error
- func IsTransient(err error) bool
- func LogAuthRejection(log *slog.Logger, what, credentialKey string, status int, body string, ...) string
- func Never[T any](ctx context.Context, opts Options, window time.Duration, attempt Attempt[T], ...) (T, error)
- func OrHeal(ctx context.Context, opts HealOptions, probe Probe, trigger Trigger) error
- func Transient(err error) error
- func Until[T any](ctx context.Context, opts Options, attempt Attempt[T], accept Accept[T]) (T, error)
- type Accept
- type Attempt
- type Counter
- type Forbidden
- type HealOptions
- type Options
- type Probe
- type Settled
- type Trigger
Constants ¶
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.
const FastInterval = 100 * time.Millisecond
FastInterval is the poll cadence floor for a wait that asks for Options.Fast.
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 ¶
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 ¶
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.
Types ¶
type Attempt ¶
Attempt produces a result. Its error means the call could not be made, not that it returned something unwanted.
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 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.