Documentation
¶
Overview ¶
Package backoff computes an exponential backoff: how long a loop that keeps failing waits before its next attempt. It is a kernel primitive — stdlib-only and domain-neutral: a duration, a growth factor, a ceiling and a jitter, with no Retry, no Job and no Delivery in a signature.
One curve serves every loop that backs off. The retry policy waits it between attempts; a supervised goroutine waits it between restarts, an outbox between deliveries, a state machine between failed transitions, a server between failed accepts. Each of those used to compute its own copy, and the copy that lived in the retry policy carried a defect the shared one fixes (ADR 0103): see Value for the float-to-duration conversion that made amd64 retries stop waiting at attempt 35.
The curve is two claims kept apart on purpose. Grow is the deterministic growth, a PURE function of the attempt; Widen is the randomisation laid on top of it. Value.Delay composes the two. A suite that cannot assert the growth without the jitter can assert neither precisely, and a caller that wants the deterministic curve asks for it by name.
How the randomisation is laid on is Value.JitterMode: added to the curve (the zero, JitterAdditive), drawn across all of it (JitterFull), or drawn from the previous wait (JitterDecorrelated) — the three variants of the AWS Builders' Library's "Timeouts, retries and backoff with jitter".
Index ¶
- func Decorrelate(base, ceiling, previous time.Duration) time.Duration
- func Full(delay time.Duration) time.Duration
- func Grow(base, ceiling time.Duration, multiplier float64, attempt int) time.Duration
- func NormalJitter(jitter float64) float64
- func NormalMultiplier(multiplier float64) float64
- func Widen(delay time.Duration, jitter float64) time.Duration
- type JitterMode
- type Value
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Decorrelate ¶ added in v0.21.0
Decorrelate returns the next wait of a "Decorrelated Jitter" backoff (AWS Builders' Library): drawn uniformly from [base, 3 × previous], held at ceiling when one is set (a zero ceiling is none) and at the longest time.Duration otherwise. A previous wait below base is read as base, so the first wait of a loop is drawn from [base, 3 × base]; a non-positive base is no wait.
It is the one variant whose wait depends on the previous wait rather than on the attempt, so the loop keeps that wait and hands it back: the bound grows with what was actually waited, and two callers that failed together drift apart instead of following one curve.
func Full ¶ added in v0.21.0
Full returns a wait drawn uniformly from [0, delay]: the "Full Jitter" of the AWS Builders' Library, laid on a delay the caller already grew and held at its ceiling. A zero or negative delay is no wait.
func Grow ¶
Grow returns the deterministic half of the curve: base × multiplier^(attempt−1), held at ceiling when one is set (a zero ceiling is none) and at the longest time.Duration otherwise. It is a PURE function of its arguments.
multiplier is normalised as NormalMultiplier does, and attempt is read as at least 1, so no input makes it return a negative duration or loop past the few dozen multiplications that reach the bound.
func NormalJitter ¶
NormalJitter clamps a jitter fraction into [0, 1], reading NaN as none.
NaN is resolved FIRST because Go's min and max propagate it: min(max(NaN, 0), 1) is NaN, and NaN <= 0 is false, so it would sail past every later guard into a float-to-integer conversion Go leaves implementation-defined.
func NormalMultiplier ¶
NormalMultiplier resolves a growth factor that would not grow — 1 or below, or NaN — to the doubling default, and returns any other factor unchanged, +Inf included: Grow holds an infinite growth at the ceiling.
func Widen ¶
Widen returns delay widened by a uniform random fraction of itself, drawn from [0, jitter) and ADDED, so the average wait keeps growing with the attempt rather than being pulled back toward zero.
jitter is normalised as NormalJitter does; zero hands delay back unchanged, and so does a delay that is zero, negative or too small to split. The widened wait stays representable: it is spread across what is left below the longest time.Duration rather than past it.
Types ¶
type JitterMode ¶ added in v0.21.0
type JitterMode uint8
JitterMode is how a Value randomises its waits: JitterAdditive (the zero), JitterFull or JitterDecorrelated.
const ( // JitterAdditive widens the grown wait by a random fraction of itself, // drawn from [0, Jitter) and ADDED: the average wait keeps growing with // the attempt. A zero Jitter is the deterministic curve. It is the zero // JitterMode. JitterAdditive JitterMode = iota // JitterFull draws the wait uniformly from [0, grown], the grown wait being // the curve held at MaxDelay: the "Full Jitter" of the Builders' Library, // which spreads callers that failed together the most widely of the three // and keeps the average at half the curve. Jitter is not read. JitterFull // does: uniformly from [BaseDelay, 3 × previous], held at MaxDelay — the // "Decorrelated Jitter" of the Builders' Library. A loop that keeps the // previous wait calls Decorrelate; [Value.Delay], which has no memory, // reads the curve's own previous step as the previous wait. Jitter is not // read. JitterDecorrelated )
The three ways a Value lays its randomisation on the curve. The zero is the additive jitter the curve has always had, so a Value written before the mode existed waits exactly as it did (ADR 0031).
func (JitterMode) String ¶ added in v0.21.0
func (m JitterMode) String() string
String renders the mode for a log line or a configuration dump: "additive", "full", "decorrelated"; a value this package does not define renders "unknown".
type Value ¶
type Value struct {
// BaseDelay is the wait after the first failure. Zero or negative waits
// nothing.
BaseDelay time.Duration
// MaxDelay is the ceiling on the grown delay. Zero means no ceiling.
MaxDelay time.Duration
// Multiplier is the growth factor between consecutive waits. 1 or below,
// and NaN, mean 2.
Multiplier float64
// Jitter widens each wait by a uniform random fraction of itself, drawn
// from [0, Jitter) and ADDED, after the MaxDelay ceiling — so when a
// ceiling is set a wait may reach MaxDelay × (1 + Jitter). Zero is the
// deterministic backoff.
//
// It exists because a deterministic backoff SYNCHRONISES the very callers
// it is meant to spread: N clients that fail against one dependency at the
// same instant retry at the same instant, N times over. Jitter turns those
// bursts back into a distribution.
//
// It is read only under JitterAdditive.
Jitter float64
// JitterMode is how the randomisation is laid on the curve. The zero,
// JitterAdditive, is the Jitter above — what Value has always done;
// JitterFull and JitterDecorrelated are the Builders' Library's two
// other variants, which randomise the whole wait and ignore Jitter. A
// value outside the three is read as JitterAdditive.
JitterMode JitterMode
}
Value is an exponential backoff: the wait after the n-th consecutive failure is BaseDelay × Multiplier^(n−1), held at MaxDelay when one is set and widened by Jitter when one is asked for.
The zero value is usable and waits nothing: a zero BaseDelay retries at once. Every other field is normalised on each call, so no value can make Delay misbehave:
- a Multiplier of 1 or below, or NaN, grows by 2 — a flat or shrinking "exponential" backoff is a fixed-interval hammer on a dependency that is already struggling;
- a zero MaxDelay is no ceiling, and the growth then stops at the longest time.Duration rather than wrapping negative;
- a Jitter outside [0, 1], or NaN, is clamped into it;
- a JitterMode this package does not define is read as JitterAdditive.
The growth is compared against its bound in the float domain BEFORE any conversion. The copy this replaces converted first: past 2^63 nanoseconds Go leaves that conversion implementation-defined — math.MinInt64 on amd64, math.MaxInt64 on arm64 — and a negative wait is below every ceiling, so a one-second retry on amd64 stopped backing off at attempt 35.
func (Value) Delay ¶
Delay returns the wait after the attempt-th consecutive failure: Grow for the curve, then the randomisation JitterMode names — Widen by Jitter for JitterAdditive, Full for JitterFull, and for JitterDecorrelated Decorrelate, reading the curve's wait for the attempt before as the previous wait (a loop that keeps its own calls Decorrelate). attempt counts from 1; a smaller value is read as 1, so a loop that has not failed yet and asks anyway waits BaseDelay rather than nothing.
It never returns a negative duration, and it costs at most as many multiplications as it takes the delay to reach its ceiling, however large attempt is.