Qubit Retry
Qubit Retry provides type-preserving retry policies for Rust sync and async operations.
The core API is Retry<E>. The retry policy is bound only to the operation error type E; the success type T is introduced by each run or run_async call.
Features
- Sync retry works without optional features.
- Async retry and per-attempt timeout are available with the
tokiofeature. qubit-configintegration is available with theconfigfeature.- Retry callbacks are stored with
rs-functionfunctors, so closures and custom function objects are both supported. AttemptFailure<E>represents one failed attempt: eitherError(E)orTimeout.RetryError<E>represents the terminal retry-flow error and carriesreason,last_failure, andRetryContext.- Lifecycle hooks are explicit:
before_attempt,on_success,on_failure, andon_error.
Installation
[]
= "0.7.0"
Enable optional integrations as needed:
[]
= { = "0.7.0", = ["tokio", "config"] }
Optional features:
tokio: enablesRetry::run_asyncand per-attempt async timeout support throughtokio::time::timeout.config: enablesRetryOptions::from_configandRetryConfigValuesfor reading retry settings fromqubit-config.
The default feature set is empty, so synchronous retry does not pull in tokio or qubit-config.
Basic Sync Retry
use Retry;
use Duration;
Failure Decisions
By default, operation errors are retried until configured attempt or elapsed-time limits stop the flow. Use retry_if_error for simple error predicates:
use ;
use Duration;
let retry = builder
.max_attempts
.exponential_backoff
.retry_if_error
.build?;
Use on_failure when decisions need access to attempt timeout, retry-after hints, or failure kind:
use ;
use Duration;
let retry = builder
.max_attempts
.fixed_delay
.on_failure
.build?;
AttemptFailureDecision::UseDefault lets the retry policy apply its configured limits, delay strategy, jitter, and optional retry-after hint.
Async Retry and Timeout
Async execution requires the tokio feature. Per-attempt timeout is configured on the builder and is reflected in AttemptFailure::Timeout plus RetryContext::attempt_timeout().
use Retry;
use Duration;
async
async
Retry-After Hints
If an attempt failure carries retry-after information, register a hint extractor with retry_after_hint. The extractor returns Option<Duration>: Some(delay) means "wait this long before the next retry", while None means "no hint is available". The default policy uses Some(delay) when all failure listeners return UseDefault; otherwise it falls back to the configured delay strategy.
use ;
use Duration;
let retry = builder
.max_attempts
.fixed_delay
.retry_after_hint
.build?;
When the hint depends only on the operation error, retry_after_from_error is a convenience wrapper around retry_after_hint:
let retry = builder
.max_attempts
.fixed_delay
.retry_after_from_error
.build?;
Listeners can also read the extracted value from RetryContext::retry_after_hint().
Listeners
Listeners are lifecycle hooks, not separate policy systems:
before_attempt: invoked before every attempt, including the first attempt.on_success: invoked after each successful attempt.on_failure: invoked after eachAttemptFailureand returnsAttemptFailureDecision.on_error: invoked once when the retry flow returns a terminalRetryError.
use ;
let retry = builder
.max_attempts
.before_attempt
.on_success
.on_failure
.on_error
.build?;
Configuration
RetryOptions is an immutable snapshot. Reading from qubit-config requires the config feature and happens during construction.
use Config;
use ;
let mut config = new;
config.set?;
config.set?;
config.set?;
config.set?;
config.set?;
config.set?;
config.set?;
let options = from_config?;
let retry = from_options?;
Supported relative keys:
max_attemptsmax_elapsed_millismax_elapsed_unlimiteddelay:none,fixed,random,exponential, orexponential_backofffixed_delay_millisrandom_min_delay_millisrandom_max_delay_millisexponential_initial_delay_millisexponential_max_delay_millisexponential_multiplierjitter_factor
Error Handling
Inspect RetryError::reason(), RetryError::last_failure(), and RetryError::context() to distinguish terminal causes from attempt failures:
use ;
let retry = builder
.max_attempts
.build?;
match retry.run