automata/retry
Types
Stateful retry context.
Bundles the immutable Policy, the number of attempts already
completed, the cumulative delay charged to the caller so far, and
the deterministic PRNG state used by jittered policies. opaque
because callers must not forge an attempt counter or rewind the
PRNG mid-sequence.
pub opaque type Context
Result of asking a Context what to do after a failure.
Retry carries the delay the caller should wait before the next
attempt and the Context to use when calling decide again.
GiveUp carries the structured reason the sequence ended.
pub type Decision {
Retry(delay: ast.Duration, next: Context)
GiveUp(reason: ast.GiveUpReason)
}
Constructors
-
Retry(delay: ast.Duration, next: Context) -
GiveUp(reason: ast.GiveUpReason)
A retry policy: pure data describing how to retry a failed operation without itself performing any I/O.
opaque so the only way to construct a value is through the
validating smart constructors (no_retry, fixed, exponential,
capped_exponential). This rules out impossible combinations such
as max_attempts <= 0 or a cap below the initial delay, and lets
us add new variants (decorrelated jitter, custom backoff, …)
without breaking pattern matches in downstream code.
pub opaque type Policy
Values
pub fn capped_exponential(
initial initial: ast.Duration,
multiplier multiplier: Int,
cap cap: ast.Duration,
max_attempts max_attempts: Int,
) -> Result(Policy, ast.RetryError)
Build an exponential backoff that saturates at cap. Equivalent
to exponential with an extra ceiling applied to every computed
delay.
pub fn capped_exponential_ms(
initial_ms initial_ms: Int,
multiplier multiplier: Int,
cap_ms cap_ms: Int,
max_attempts max_attempts: Int,
) -> Result(Policy, ast.RetryError)
Build a capped exponential policy directly from raw millisecond
integers, skipping the explicit ast.from_milliseconds calls that
capped_exponential/4 would otherwise require. Useful when both
the initial delay and the cap are compile-time literals — the
common case at a call site.
Validation runs in the same order as capped_exponential/4: the
initial_ms duration is constructed first, then cap_ms, then the
underlying capped_exponential validates multiplier, max_attempts,
and the cap >= initial invariant. A bad value at any step short-
circuits with the corresponding RetryError.
Callers that already hold runtime-derived Duration values (for
example, from a config parser that ran ast.from_minutes) should
keep using capped_exponential/4 and pass the values through
unchanged.
pub fn cumulative_delay(ctx ctx: Context) -> ast.Duration
Total of the delays the policy has handed out so far. Useful for applying an upper-bound deadline at the call site.
pub fn current_attempt(ctx ctx: Context) -> Int
Number of attempts already completed in this sequence.
pub fn decide(
ctx ctx: Context,
failure failure: ast.FailureKind,
) -> Decision
Decide what to do after a failed attempt.
Permanent short-circuits regardless of the policy. Transient
consults the policy: it may yield a retry delay, the context to
use next, or a structured reason to stop.
pub fn exponential(
initial initial: ast.Duration,
multiplier multiplier: Int,
max_attempts max_attempts: Int,
) -> Result(Policy, ast.RetryError)
Build an unbounded exponential backoff: the next delay is
initial * multiplier ^ (attempt - 1), where attempt is the
upcoming try number (1-based).
pub fn fixed(
delay delay: ast.Duration,
max_attempts max_attempts: Int,
) -> Result(Policy, ast.RetryError)
Build a fixed-delay policy: every retry waits exactly delay,
up to max_attempts total tries.
pub fn next_delay(
ctx ctx: Context,
failure failure: ast.FailureKind,
) -> Result(ast.Duration, ast.GiveUpReason)
Convenience accessor: the delay component of decide, or the
give-up reason as Error.
Does not advance the context — call decide if you intend to act
on the answer.
pub fn no_retry() -> Policy
Build a policy that never retries. Any failure (transient or
permanent) ends the sequence on the first call to decide.
pub fn should_retry(
ctx ctx: Context,
failure failure: ast.FailureKind,
) -> Bool
Convenience predicate: True when decide would return Retry.
Does not advance the context — call decide if you intend to act
on the answer.
pub fn start(policy policy: Policy, seed seed: Int) -> Context
Begin a new retry sequence.
seed deterministically drives any jitter the policy applies. Use
the same seed twice and you get the same delay sequence, on the
BEAM and on the JavaScript target alike.
pub fn with_jitter(
policy policy: Policy,
jitter jitter: ast.Jitter,
) -> Policy
Decorate an existing policy with a jitter strategy.
no_retry is unaffected (jitter has nothing to spread).