Crate tryhard

source ·
Expand description

Easily retry futures.

Example usage

// some async function that can fail
async fn read_file(path: &str) -> Result<String, std::io::Error> {
    // ...
}

let contents = tryhard::retry_fn(|| read_file("Cargo.toml"))
    // retry at most 10 times
    .retries(10)
    .await?;

assert!(contents.contains("tryhard"));

You can also customize which backoff strategy to use and what the max retry delay should be:

use std::time::Duration;

let contents = tryhard::retry_fn(|| read_file("Cargo.toml"))
    .retries(10)
    .exponential_backoff(Duration::from_millis(10))
    .max_delay(Duration::from_secs(1))
    .await?;

assert!(contents.contains("tryhard"));

Retrying several futures in the same way

Using RetryFutureConfig you’re able to retry several futures in the same way:

use tryhard::RetryFutureConfig;

let config = RetryFutureConfig::new(10)
    .exponential_backoff(Duration::from_millis(10))
    .max_delay(Duration::from_secs(3));

tryhard::retry_fn(|| read_file("Cargo.toml"))
    .with_config(config)
    .await?;

// retry another future in the same way
tryhard::retry_fn(|| read_file("src/lib.rs"))
    .with_config(config)
    .await?;

How many times will my future run?

The future is always run at least once, so if you do .retries(0) your future will run once. If you do .retries(10) and your future always fails it’ll run 11 times.

Why do you require a closure?

Due to how futures work in Rust you’re not able to retry a bare F where F: Future. A future can possibly fail at any point in its execution and might be in an inconsistent state after the failing. Therefore retrying requires making a fresh future for each attempt.

This means you cannot move values into the closure that produces the futures. You’ll have to clone instead:

async fn future_with_owned_data(data: Vec<u8>) -> Result<(), std::io::Error> {
    // ...
}

let data: Vec<u8> = vec![1, 2, 3];

tryhard::retry_fn(|| {
    // We need to clone `data` here. Otherwise we would have to move `data` into the closure.
    // `move` closures can only be called once (they only implement `FnOnce`)
    // and therefore cannot be used to create more than one future.
    let data = data.clone();

    async {
        future_with_owned_data(data).await
    }
}).retries(10).await?;

Be careful what you retry

This library is meant to make it straight forward to retry simple futures, such as sending a single request to some service that occationally fails. If you have some complex operation that consists of multiple futures each of which can fail, this library might be not appropriate. You risk repeating the same operation more than once because some later operation keeps failing.

Tokio only for now

This library currently expects to be used from within a tokio runtime. That is because it makes use of async timers. Feel free to open an issue if you need support for other runtimes.

Modules

Structs

Enums

Traits

  • Trait allowing you to run some future when a retry occurs. Could for example to be used for logging or other kinds of telemetry.

Functions

  • Create a RetryFn which produces retryable futures.