aws/retry
Retry middleware. Wraps any http_send.Send with the retry semantics
the AWS Smithy runtime ships
(aws-sdk-rust/…/client/retries/strategy/standard.rs).
Behaviour mirrored from upstream:
- Exponential backoff with full jitter:
min(initial * 2^attempt, max) * rand(). Matchescalculate_exponential_backoffin Rust. - Status-code classifier: 2xx success; 4xx (except 408/429) is
non-retryable; 408/429/5xx retryable; transport errors classified
as
TransientError(timeout-class) and charged at the highertimeout_retry_cost. Per AWS-SmithyErrorKindmapping. Retry-Afterheader (integer seconds) overrides the computed backoff and is clamped tomax_delay.- Token bucket gates retries (
try_acquire(cost)); each retry holds a permit across attempts that is released on success / final non-retryable outcome, or replaced before the next retry. Matches Rust’sset_retry_permit/release_retry_permit.
Deliberately out of scope (explicit deltas vs Rust SDK, documented for the M4 audit):
- Time-based bucket refill (
refill_ratein Rust SDK). Our bucket only changes on acquire/release/reward. - Pre-request rate limiter gating
(
should_attempt_initial_request). Only retries are gated. - The CUBIC
ClientRateLimiter— Rust ships this as a separate component for “true” adaptive mode. Ouradaptivebuilder installs the token bucket only; CUBIC is a follow-up milestone.
Types
What the strategy decided after looking at the last attempt.
pub type Decision {
Stop
RetryAfter(delay_ms: Int, cost: Int)
GiveUp
}
Constructors
-
StopAttempt succeeded — return the response to the caller.
-
RetryAfter(delay_ms: Int, cost: Int)Attempt failed retryably — sleep
delay_msthen attempt again.costis the number of tokens the bucket should debit before the retry. -
GiveUpAttempt failed non-retryably or attempts are exhausted.
Values
pub fn adaptive(bucket bucket: rate_limiter.Bucket) -> Strategy
Standard retry with a token-bucket gate. Equivalent to Rust SDK “adaptive” minus the CUBIC client rate limiter (see module docs).
pub fn adaptive_with(
bucket bucket: rate_limiter.Bucket,
max_attempts max_attempts: Int,
base_delay_ms base_delay_ms: Int,
max_delay_ms max_delay_ms: Int,
sleep sleep: fn(Int) -> Nil,
rng rng: fn() -> Float,
) -> Strategy
pub fn classify(
result: Result(response.Response(BitArray), http_send.HttpError),
attempt: Int,
strategy: Strategy,
) -> Decision
Classify one attempt’s outcome. Exposed for test asserting and so the protocol-codec layer (M5) can override per-service error semantics.
pub const default_base_delay_ms: Int
Base delay before the first retry, in milliseconds.
pub const default_max_attempts: Int
Default maximum total attempts. Matches AWS SDK “standard” mode.
pub const default_max_delay_ms: Int
Cap on any individual retry delay, in milliseconds. Matches Rust SDK’s
MAX_BACKOFF (20 s).
pub fn exponential_backoff(
strategy: Strategy,
attempt: Int,
) -> Int
Exponential backoff with full jitter, mirrored from Rust SDK
calculate_exponential_backoff:
raw = base * 2^(attempt-1) bound = min(raw, max) delay = rng() * bound
attempt is 1-indexed (the first retry uses base * 2^0 = base).
pub const retry_cost: Int
Token cost for a normal retryable error (throttling, server fault,
retryable client). Matches Rust SDK DEFAULT_RETRY_COST = 5.
pub fn standard_with(
max_attempts max_attempts: Int,
base_delay_ms base_delay_ms: Int,
max_delay_ms max_delay_ms: Int,
sleep sleep: fn(Int) -> Nil,
rng rng: fn() -> Float,
) -> Strategy
pub const timeout_retry_cost: Int
Token cost for a transient / timeout retry. Matches Rust SDK
DEFAULT_RETRY_TIMEOUT_COST = DEFAULT_RETRY_COST * 2 = 10.
pub fn with_base_delay_ms(
strategy: Strategy,
base_delay_ms: Int,
) -> Strategy
Override the initial-retry-backoff base on an existing
Strategy. The first retry sleeps roughly base_delay_ms with
full jitter; each subsequent attempt doubles the cap up to
max_delay_ms. Set to 0 (paired with with_max_delay_ms(0))
for tests that want zero-delay retries.
pub fn with_max_attempts(
strategy: Strategy,
max_attempts: Int,
) -> Strategy
Override the per-request max attempt count on an existing
Strategy, preserving the other knobs (delays, sleep / rng /
rate-limiter). The common case for tuning retry behaviour —
callers usually want the AWS-recommended backoff curve but a
different attempt budget (1 for fail-fast tests, 5 for
long-running batch workloads, etc.).
Strategy is opaque so this setter lives here; the runtime
re-exports a with_max_attempts(config, n) convenience that
threads through to this setter.
pub fn with_max_delay_ms(
strategy: Strategy,
max_delay_ms: Int,
) -> Strategy
Override the maximum-backoff cap on an existing Strategy.
Pairs with with_base_delay_ms for tests that need zero or
very short delays; production callers usually keep the AWS-
recommended default.
pub fn with_retry(
send send: fn(request.Request(BitArray)) -> Result(
response.Response(BitArray),
http_send.HttpError,
),
strategy strategy: Strategy,
) -> fn(request.Request(BitArray)) -> Result(
response.Response(BitArray),
http_send.HttpError,
)
Wrap a Send with retry semantics.