#retry #tokio #backoff #behavior #async

tokio-retry2

Extensible, asynchronous retry behaviours for futures/tokio

6 releases

new 0.5.4 Sep 25, 2024
0.5.3 Sep 18, 2024
0.4.0 Sep 4, 2024

#312 in Asynchronous

Download history 116/week @ 2024-08-30 27/week @ 2024-09-06 686/week @ 2024-09-13 3132/week @ 2024-09-20

3,961 downloads per month
Used in 11 crates (2 directly)

MIT license

46KB
976 lines

tokio-retry2

Forked from https://github.com/srijs/rust-tokio-retry to keep it up-to-date

Extensible, asynchronous retry behaviours for the ecosystem of tokio libraries.

Crates.io dependency status codecov

Documentation

Installation

Add this to your Cargo.toml:

[dependencies]
tokio-retry2 = { version = "0.5", features = ["jitter"] }

Examples

use tokio_retry2::{Retry, RetryError};
use tokio_retry2::strategy::{ExponentialBackoff, jitter, MaxInterval};

async fn action() -> Result<u64, RetryError<()>> {
    // do some real-world stuff here...
    RetryError::to_transient(())
}

#[tokio::main]
async fn main() -> Result<(), ()> {
    let retry_strategy = ExponentialBackoff::from_millis(10)
        .factor(1) // multiplication factor applied to deplay
        .max_delay_millis(100) // set max delay between retries to 500ms
        .max_interval(10000) // set max interval to 10 seconds
        .map(jitter) // add jitter to delays
        .take(3);    // limit to 3 retries

    let result = Retry::spawn(retry_strategy, action).await?;

    Ok(())
}

Or, to retry with a notification function:

use tokio_retry2::{Retry, RetryError};
use tokio_retry2::strategy::{ExponentialBackoff, jitter, MaxInterval};

async fn action() -> Result<u64, RetryError<std::io::Error>> {
    // do some real-world stuff here...
    RetryError::to_permanent(()) // Early exits on this error
}

fn notify(err: &std::io::Error, duration: std::time::Duration) {
    tracing::info!("Error {err:?} occurred at {duration:?}");
}

#[tokio::main]
async fn main() -> Result<(), ()> {
    let retry_strategy = ExponentialBackoff::from_millis(10)
        .factor(1) // multiplication factor applied to deplay
        .max_delay_millis(100) // set max delay between retries to 500ms
        .max_interval(10000) // set max interval to 10 seconds
        .map(jitter) // add jitter to delays
        .take(3);    // limit to 3 retries

    let result = Retry::spawn_notify(retry_strategy, action, notify).await?;

    Ok(())
}

Early Exit and Error Handling

Actions must return a RetryError that can wrap any other error type. There are 2 RetryError error trypes:

  • Permanent, which receives an error and brakes the retry loop. It can be constructed manually or with auxiliary functions RetryError::permanent(e: E), that returns a RetryError::Permanent<E>, or RetryError::to_permanent(e: E), that returns an Err(RetryError::Permanent<E>).
  • Transient, which is the Default error for the loop. It has 2 modes:
    1. RetryError::transient(e: E) and RetryError::to_transient(e: E), that return a RetryError::Transient<E>, which is an error that triggers the retry strategy.
    2. RetryError::retry_after(e: E, duration: std::time::Duration) and RetryError::to_retry_after(e: E, duration: std::time::Duration), that return a RetryError::Transient<E>, which is an error that triggers the retry strategy after the specified duration.
  • Thet is also the trait MapErr that possesses 2 auxiliary functions that map the current function Result to Result<T, RetryError<E>>:
    1. fn map_transient_err(self) -> Result<T, RetryError<E>>;
    2. fn map_permanent_err(self) -> Result<T, RetryError<E>>;
  • Using the ? operator on an Option type will always propagate a RetryError::Transient<E> with no extra duration.

Dependencies

~2.4–8.5MB
~67K SLoC