Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement retry support #2

Merged
merged 5 commits into from
Apr 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ description = "Backoff policies"

[dependencies]
rand = "0.8.5"
futures = "0.3.21"
tokio = { version = "1.17.0", features = ["time"] }
pin-project = "1.0.10"

[dev-dependencies]
tokio = { version = "1.17.0", features = ["full"] }
Expand Down
22 changes: 8 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,23 @@ The opposite backoff implementation of the popular [backoff](https://docs.rs/bac

- Newer: developed by Rust edition 2021 and latest stable.
- Cleaner: Iterator based abstraction, easy to use, customization friendly.
- Smaller: Focused on backoff implementation, no need to play with runtime specific features.
- Easier: Trait based implementations, works like a native function provided by closures.

## Quick Start

```rust
use backon::Retryable;
use backon::ExponentialBackoff;
use anyhow::Result;

async fn fetch() -> Result<String> {
Ok(reqwest::get("https://www.rust-lang.org").await?.text().await?)
}

#[tokio::main]
async fn main() -> Result<()> {
for delay in ExponentialBackoff::default() {
let x = reqwest::get("https://www.rust-lang.org").await?.text().await;
match x {
Ok(v) => {
println!("Successfully fetched");
break;
},
Err(_) => {
tokio::time::sleep(delay).await;
continue
}
};
}
let content = fetch.retry(ExponentialBackoff::default()).await?;
println!("fetch succeeded: {}", contet);

Ok(())
}
Expand Down
8 changes: 8 additions & 0 deletions src/backoff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
use std::time::Duration;

/// Backoff is an [`Iterator`] that returns [`Duration`].
///
/// - `Some(Duration)` means caller need to `sleep(Duration)` and retry the same request
/// - `None` means we have reaching the limits, caller needs to return current error instead.
pub trait Backoff: Iterator<Item = Duration> + Clone {}
impl<T> Backoff for T where T: Iterator<Item = Duration> + Clone {}
28 changes: 28 additions & 0 deletions src/constant.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
use std::time::Duration;

/// ConstantBackoff provides backoff with constant delay and limited times.
///
/// # Default
///
/// - delay: 1s
/// - max times: 3
///
/// # Examples
///
/// ```no_run
/// use backon::Retryable;
/// use backon::ConstantBackoff;
/// use anyhow::Result;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org").await?.text().await?)
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// let content = fetch.retry(ConstantBackoff::default()).await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
pub struct ConstantBackoff {
delay: Duration,
max_times: Option<usize>,
Expand All @@ -18,11 +44,13 @@ impl Default for ConstantBackoff {
}

impl ConstantBackoff {
/// Set delay of current backoff.
pub fn with_delay(mut self, delay: Duration) -> Self {
self.delay = delay;
self
}

/// Set max times of current backoff.
pub fn with_max_times(mut self, max_times: usize) -> Self {
self.max_times = Some(max_times);
self
Expand Down
48 changes: 33 additions & 15 deletions src/exponential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,34 @@ use std::time::Duration;

/// Exponential backoff implementation.
///
/// # Default
///
/// - jitter: false
/// - factor: 2
/// - min_delay: 1s
/// - max_delay: 60s
/// - max_times: 3
///
/// # Examples
///
/// ```
/// ```no_run
/// use backon::Retryable;
/// use backon::ExponentialBackoff;
/// use anyhow::Result;
///
/// async fn fetch() -> Result<String> {
/// Ok(reqwest::get("https://www.rust-lang.org").await?.text().await?)
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<()> {
/// for delay in ExponentialBackoff::default() {
/// let x = reqwest::get("https://www.rust-lang.org").await?.text().await;
/// match x {
/// Ok(v) => {
/// println!("Successfully fetched");
/// break;
/// },
/// Err(_) => {
/// tokio::time::sleep(delay).await;
/// continue
/// }
/// };
/// }
/// let content = fetch.retry(ExponentialBackoff::default()).await?;
/// println!("fetch succeeded: {}", content);
///
/// Ok(())
/// }
/// ```
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct ExponentialBackoff {
jitter: bool,
factor: f32,
Expand Down Expand Up @@ -56,28 +58,44 @@ impl Default for ExponentialBackoff {
}

impl ExponentialBackoff {
/// Set jitter of current backoff.
///
/// If jitter is enabled, ExponentialBackoff will add a random jitter in `[0, min_delay)
/// to current delay.
pub fn with_jitter(mut self) -> Self {
self.jitter = true;
self
}

/// Set factor of current backoff.
///
/// # Panics
///
/// This function will panic if input factor smaller than `1.0`.
pub fn with_factor(mut self, factor: f32) -> Self {
debug_assert!(factor > 1.0, "invalid factor that lower than 1");

self.factor = factor;
self
}

/// Set min_delay of current backoff.
pub fn with_min_delay(mut self, min_delay: Duration) -> Self {
self.min_delay = min_delay;
self
}

/// Set max_delay of current backoff.
///
/// Delay will not increasing if current delay is larger than max_delay.
pub fn with_max_delay(mut self, max_delay: Duration) -> Self {
self.max_delay = Some(max_delay);
self
}

/// Set max_times of current backoff.
///
/// Backoff will return `None` if max times is reaching.
pub fn with_max_times(mut self, max_times: usize) -> Self {
self.max_times = Some(max_times);
self
Expand Down
44 changes: 42 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,46 @@
//! backon intends to provide an opposite backoff implementation of the popular [backoff](https://docs.rs/backoff).
//!
//! - Newer: developed by Rust edition 2021 and latest stable.
//! - Cleaner: Iterator based abstraction, easy to use, customization friendly.
//! - Easier: Trait based implementations, works like a native function provided by closures.
//!
//! # Backoff
//!
//! Any types that implements `Iterator<Item = Duration>` can be used as backoff.
//!
//! backon also provides backoff implementations with reasonable defaults:
//!
//! - [`ConstantBackoff`]: backoff with constant delay and limited times.
//! - [`ExponentialBackoff`]: backoff with exponential delay, also provides jitter supports.
//!
//! # Examples
//!
//! ```no_run
//! use backon::Retryable;
//! use backon::ExponentialBackoff;
//! use anyhow::Result;
//!
//! async fn fetch() -> Result<String> {
//! Ok(reqwest::get("https://www.rust-lang.org").await?.text().await?)
//! }
//!
//! #[tokio::main]
//! async fn main() -> Result<()> {
//! let content = fetch.retry(ExponentialBackoff::default()).await?;
//! println!("fetch succeeded: {}", content);
//!
//! Ok(())
//! }
//! ```

mod backoff;
pub use backoff::Backoff;

mod constant;
pub use constant::ConstantBackoff;

mod exponential;
pub use exponential::ExponentialBackoff;
mod policy;
pub use policy::Policy;

mod retry;
pub use retry::Retryable;
4 changes: 0 additions & 4 deletions src/policy.rs

This file was deleted.

Loading