Skip to content
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
1 change: 1 addition & 0 deletions .github/workflows/pull_request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
pull_request:
types:
- opened
- synchronize
paths:
- Cargo.toml
- '**/Cargo.toml'
Expand Down
13 changes: 13 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
hooks:
# - id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace
- repo: https://github.com/doublify/pre-commit-rust
rev: v1.0
hooks:
- id: fmt
- id: clippy
args: [ --all-targets, --, -D, clippy::all ]
19 changes: 15 additions & 4 deletions examples/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async fn main() {
let world = tokio::spawn(async move {
mulligan::until(|r| r.is_ok())
.stop_after(10)
.full_jitter()
.jitter(mulligan::jitter::Full)
.fixed(Duration::from_secs(1))
.retry(|| async { this_errors("world").await })
.await
Expand All @@ -30,7 +30,12 @@ async fn main() {
.stop_after(10)
.full_jitter()
.fixed(Duration::from_millis(200))
.on_retry(|res, attempt| { println!("[retry] start to call retry(): attempt = {}, prev = {:?}", attempt, res) })
.on_retry(|res, attempt| {
println!(
"[retry] start to call retry(): attempt = {}, prev = {:?}",
attempt, res
)
})
.retry(|| async { this_errors("[retry] running").await })
.await
});
Expand All @@ -44,8 +49,14 @@ async fn main() {
.stop_after(3)
.full_jitter()
.fixed(Duration::from_millis(200))
.on_retry(|res, attempt| { println!("[on_retry] start to call retry() again. In last attempt = {}, result = {:?}", attempt, res) })
.on_retry(|res, attempt| {
println!(
"[on_retry] start to call retry() again. In last attempt = {}, result = {:?}",
attempt, res
)
})
.retry(|| async { this_errors("[retry] call `.retry()` and failed").await })
.await
}).await;
})
.await;
}
57 changes: 57 additions & 0 deletions src/backoff.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use std::time::Duration;

pub trait Backoff {
fn delay(&self, attempt: u32) -> Duration;
fn base(&self) -> Duration;
}

pub struct Fixed(Duration);

impl Fixed {
pub fn base(dur: Duration) -> Self {
Self(dur)
}
}

impl Backoff for Fixed {
fn base(&self) -> Duration {
self.0
}
fn delay(&self, _attempt: u32) -> Duration {
self.0
}
}

pub struct Linear(Duration);

impl Linear {
pub fn base(dur: Duration) -> Self {
Self(dur)
}
}

impl Backoff for Linear {
fn base(&self) -> Duration {
self.0
}
fn delay(&self, attempt: u32) -> Duration {
self.0 * attempt
}
}

pub struct Exponential(Duration);

impl Exponential {
pub fn base(dur: Duration) -> Self {
Self(dur)
}
}

impl Backoff for Exponential {
fn base(&self) -> Duration {
self.0
}
fn delay(&self, attempt: u32) -> Duration {
self.0 * 2u32.pow(attempt)
}
}
55 changes: 55 additions & 0 deletions src/jitter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use std::time::Duration;

use rand::Rng;

pub trait Jitter {
fn jitter(&mut self, delay: Duration, max: Option<Duration>) -> Duration;
}

pub struct NoJitter;

impl Jitter for NoJitter {
fn jitter(&mut self, delay: Duration, max: Option<Duration>) -> Duration {
max.map_or(delay, |max| max.min(delay))
}
}

pub struct Full;

impl Jitter for Full {
fn jitter(&mut self, delay: Duration, max: Option<Duration>) -> Duration {
let capped = max.map_or(delay, |max| max.min(delay));
rand::thread_rng().gen_range(Duration::from_micros(0)..=capped)
}
}

pub struct Equal;

impl Jitter for Equal {
fn jitter(&mut self, delay: Duration, max: Option<Duration>) -> Duration {
let capped = max.map_or(delay, |max| max.min(delay));
rand::thread_rng().gen_range((capped / 2)..=capped)
}
}

pub struct Decorrelated {
base: Duration,
previous: Duration,
}

impl Decorrelated {
pub fn base(dur: Duration) -> Self {
Self {
base: dur,
previous: Duration::from_secs(0),
}
}
}

impl Jitter for Decorrelated {
fn jitter(&mut self, delay: Duration, max: Option<Duration>) -> Duration {
self.previous = delay; // TODO: Need to check if this is correct?
let next = rand::thread_rng().gen_range(self.base..=self.previous * 3);
max.map_or_else(|| next, |max| max.min(next))
}
}
Loading