Skip to main content

Crate ease_off

Crate ease_off 

Source
Expand description

An easy, opinionated exponential backoff implementation.

Benefits over other implementations:

  • More flexible control flow (you implement the loop yourself).
  • Opinionated but sane defaults
    • Explicitly choose deadline, timeout or unlimited, so you know exactly what’s going to happen.
  • RetryableError trait allows for more reusable code.
  • Immutable core API for when stateful backoffs aren’t appropriate.

§Examples

§Blocking Operation

(Source: examples/blocking.rs)

use ease_off::RetryableError;
use std::time::Duration;

struct FallibleOperation {
    tries_required: usize,
}

struct Success {
    message: String,
}

#[derive(Debug)]
pub struct Error {
    pub message: String,
}

impl RetryableError for Error {
    fn can_retry(&self) -> bool {
        true
    }
}

impl FallibleOperation {
    fn try_op(&mut self) -> Result<Success, Error> {
        if self.tries_required > 0 {
            let remaining = self.tries_required;
            self.tries_required -= 1;

            Err(Error {
                message: format!("failure! tries remaining: {remaining}"),
            })
        } else {
            Ok(Success {
                message: "yay! completed successfully!".to_string(),
            })
        }
    }
}

const OPTIONS: ease_off::Options = ease_off::Options::new()
    // Set a longer `initial_delay` so we can see it working
    .initial_delay(Duration::from_secs(1));

fn main() -> Result<(), Error> {
    let mut fallible = FallibleOperation { tries_required: 3 };

    // If you just want to use the default options:
    // let mut ease_off = EaseOff::start_timeout(Duration::from_secs(60));
    let mut ease_off = OPTIONS.start_timeout(Duration::from_secs(60));

    loop {
        let Some(Success { message }) = ease_off
            .try_blocking(|| fallible.try_op())
            .inspect_err(|e| println!("error: {e:?}"))
            .or_retry()?
        else {
            continue;
        };

        println!("completed with message: {message}");
        break;
    }

    Ok(())
}

§Async Operation (Tokio)

(Source: examples/tokio.rs)

use ease_off::RetryableError;
use std::time::Duration;

struct FallibleOperation {
    tries_required: usize,
}

struct Success {
    message: String,
}

#[derive(Debug)]
pub struct Error {
    pub message: String,
}

impl RetryableError for Error {
    fn can_retry(&self) -> bool {
        true
    }
}

impl FallibleOperation {
    async fn try_op(&mut self) -> Result<Success, Error> {
        if self.tries_required > 0 {
            let remaining = self.tries_required;
            self.tries_required -= 1;

            Err(Error {
                message: format!("failure! tries remaining: {remaining}"),
            })
        } else {
            Ok(Success {
                message: "yay! completed successfully!".to_string(),
            })
        }
    }
}

const OPTIONS: ease_off::Options = ease_off::Options::new()
    // Set a longer `initial_delay` so we can see it working
    .initial_delay(Duration::from_secs(1));

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Error> {
    let mut fallible = FallibleOperation { tries_required: 3 };

    // If you just want to use the default options:
    // let mut ease_off = EaseOff::start_timeout(Duration::from_secs(60));
    let mut ease_off = OPTIONS.start_timeout(Duration::from_secs(60));

    loop {
        let Some(Success { message }) = ease_off
            .try_async(fallible.try_op())
            .await
            .inspect_err(|e| println!("error: {e:?}"))
            .or_retry()?
        else {
            continue;
        };

        println!("completed with message: {message}");
        break;
    }

    Ok(())
}

Modules§

core
Immutable core backoff API, without error management or sleeps.
futuresasync-io-2 or tokio
Backoff support for async/await.

Structs§

EaseOff
Exponential backoff controller.
Options
Configuration options for EaseOff and EaseOffCore.
ResultWrapper
Wrapper for Result returned from methods on EaseOff.
TimeoutError
Error wrapper type indicating a failure due to a deadline elapsing.

Enums§

Error
Error type for EaseOff which includes the fatality level of the error.

Traits§

RetryableError
Trait which may be implemented for error types to enable code reuse with EaseOff.