Expand description

retry-block provides utilities to retry operations that may fail with configurable backoff behavior.

Usage

Retry an operation using the corresponding retry macro or retry_fn function. The macro accepts an iterator over Durations and a block that returns a Result (or OperationResult; see below). The iterator is used to determine how long to wait after each unsuccessful try and how many times to try before giving up and returning Result::Err. The block determines either the final successful value, or an error value, which can either be returned immediately or used to indicate that the operation should be retried.

Any type that implements IntoIterator<Item = Duration> can be used to determine retry behavior, though a few useful implementations are provided in the delay module, including a fixed delay and exponential back-off.

The Iterator API can be used to limit or modify the delay strategy. For example, to limit the number of retries to 1:


let mut collection = vec![1, 2, 3].into_iter();

let result = retry!(Fixed::new(Duration::from_millis(100)).take(1), {
    match collection.next() {
        Some(n) if n == 3 => Ok("n is 3!"),
        Some(_) => Err("n must be 3!"),
        None => Err("n was never 3!"),
    }
});

assert!(result.is_err());

The RetryConfig struct can be used to retry an operation with a serializable retry config that specifies an amount of retries and a random backoff interval.


let config = RetryConfig {
    count: 1,
    min_backoff: 100,
    max_backoff: 300,
};
let mut collection = vec![1, 2, 3].into_iter();

let result = retry!(config, {
    match collection.next() {
        Some(n) if n == 3 => Ok("n is 3!"),
        Some(_) => Err("n must be 3!"),
        None => Err("n was never 3!"),
    }
});

assert!(result.is_err());

Random jitter is applied by default to any delay strategy, but you can make it fixed using exact or add random jitter to any delay strategy using the jitter function:


let mut collection = vec![1, 2, 3].into_iter();

let result = retry_fn(Exponential::exact(Duration::from_millis(10)).map(jitter).take(3), || {
    match collection.next() {
        Some(n) if n == 3 => Ok("n is 3!"),
        Some(_) => Err("n must be 3!"),
        None => Err("n was never 3!"),
    }
});

assert!(result.is_ok());

To deal with fatal errors, return retry_block::OperationResult, which is like std’s Result, but with a third case to distinguish between errors that should cause a retry and errors that should immediately return, halting retry behavior. (Internally, OperationResult is always used, and closures passed to retry that return plain Result are converted into OperationResult.)


let mut collection = vec![1, 2].into_iter();
let value = retry!(Fixed::new(Duration::from_millis(1)), {
    match collection.next() {
        Some(n) if n == 2 => OperationResult::Ok(n),
        Some(_) => OperationResult::Retry("not 2"),
        None => OperationResult::Err("not found"),
    }
}).unwrap();

assert_eq!(value, 2);

Features

  • random: offer some random delay utilities (on by default)
  • config: offer serializable retry config (on by default)
  • future: offer asynchronous retry mechanisms (on by default)

Re-exports

pub use future::*;

Modules

Different types of delay for retryable operations.

Asychronous versions of the common retry functions

Tools for persistent retries that save the retry status to be continued on a restart

Macros

Retry a block with tokio::time::sleep

Retry an operation forever with exponential delay until it succeeds

Retry a block with std::thread::sleep

Retry an operation forever with exponential delay until it succeeds

Structs

A serializable retry configuration for a random range and finite retry count

Enums

Functions

Retry the given operation until it succeeds, or until the given Duration iterator ends.