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: Add std support for no-std without global rand seed #169

Merged
merged 14 commits into from
Dec 17, 2024
10 changes: 6 additions & 4 deletions backon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ targets = [
]

[features]
default = ["std-blocking-sleep", "tokio-sleep", "gloo-timers-sleep"]
default = ["std", "std-blocking-sleep", "tokio-sleep", "gloo-timers-sleep"]
std = ["fastrand/std"]
std-blocking-sleep = []
gloo-timers-sleep = ["dep:gloo-timers", "gloo-timers?/futures"]
tokio-sleep = ["dep:tokio", "tokio?/time"]
gloo-timers-sleep = ["gloo-timers/futures"]
tokio-sleep = ["tokio/time"]

[dependencies]
fastrand = "2"
fastrand = { version = "2", default-features = false }
embassy-time = { version = "0.3", optional = true }
Xuanwo marked this conversation as resolved.
Show resolved Hide resolved

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", optional = true }
Expand Down
26 changes: 23 additions & 3 deletions backon/src/backoff/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub struct ConstantBuilder {
delay: Duration,
max_times: Option<usize>,
jitter: bool,
seed: Option<u64>,
}

impl Default for ConstantBuilder {
Expand All @@ -44,6 +45,7 @@ impl Default for ConstantBuilder {
delay: Duration::from_secs(1),
max_times: Some(3),
jitter: false,
seed: None,
}
}
}
Expand All @@ -61,14 +63,20 @@ impl ConstantBuilder {
self
}

/// Set jitter for the backoff.
/// Enable jitter for the backoff.
///
/// Jitter is a random value added to the delay to prevent a thundering herd problem.
pub fn with_jitter(mut self) -> Self {
self.jitter = true;
self
}

/// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}

/// Set no max times for the backoff.
///
/// The backoff will not stop by itself.
Expand All @@ -90,6 +98,17 @@ impl BackoffBuilder for ConstantBuilder {

attempts: 0,
jitter: self.jitter,
rng: if let Some(seed) = self.seed {
fastrand::Rng::with_seed(seed)
} else {
#[cfg(feature = "std")]
let rng = fastrand::Rng::new();

#[cfg(not(feature = "std"))]
let rng = fastrand::Rng::with_seed(super::RANDOM_SEED);

rng
},
}
}
}
Expand All @@ -113,14 +132,15 @@ pub struct ConstantBackoff {

attempts: usize,
jitter: bool,
rng: fastrand::Rng,
}

impl Iterator for ConstantBackoff {
type Item = Duration;

fn next(&mut self) -> Option<Self::Item> {
let delay = || match self.jitter {
true => self.delay + self.delay.mul_f32(fastrand::f32()),
let mut delay = || match self.jitter {
true => self.delay + self.delay.mul_f32(self.rng.f32()),
false => self.delay,
};
match self.max_times {
Expand Down
27 changes: 25 additions & 2 deletions backon/src/backoff/exponential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pub struct ExponentialBuilder {
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
seed: Option<u64>,
}

impl Default for ExponentialBuilder {
Expand All @@ -51,12 +52,13 @@ impl Default for ExponentialBuilder {
min_delay: Duration::from_secs(1),
max_delay: Some(Duration::from_secs(60)),
max_times: Some(3),
seed: None,
}
}
}

impl ExponentialBuilder {
/// Set the jitter for the backoff.
/// Enable jitter for the backoff.
///
/// When jitter is enabled, [`ExponentialBackoff`] will add a random jitter within `(0, min_delay)`
/// to the current delay.
Expand All @@ -65,6 +67,12 @@ impl ExponentialBuilder {
self
}

/// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}

/// Set the factor for the backoff.
///
/// # Panics
Expand Down Expand Up @@ -126,6 +134,17 @@ impl BackoffBuilder for ExponentialBuilder {
fn build(self) -> Self::Backoff {
ExponentialBackoff {
jitter: self.jitter,
rng: if let Some(seed) = self.seed {
fastrand::Rng::with_seed(seed)
} else {
#[cfg(feature = "std")]
let rng = fastrand::Rng::new();

#[cfg(not(feature = "std"))]
let rng = fastrand::Rng::with_seed(super::RANDOM_SEED);

rng
},
factor: self.factor,
min_delay: self.min_delay,
max_delay: self.max_delay,
Expand All @@ -152,6 +171,7 @@ impl BackoffBuilder for &ExponentialBuilder {
#[derive(Debug)]
pub struct ExponentialBackoff {
jitter: bool,
rng: fastrand::Rng,
wackazong marked this conversation as resolved.
Show resolved Hide resolved
factor: f32,
min_delay: Duration,
max_delay: Option<Duration>,
Expand Down Expand Up @@ -194,7 +214,7 @@ impl Iterator for ExponentialBackoff {
};
// If jitter is enabled, add random jitter based on min delay.
if self.jitter {
tmp_cur = tmp_cur.saturating_add(self.min_delay.mul_f32(fastrand::f32()));
tmp_cur = tmp_cur.saturating_add(self.min_delay.mul_f32(self.rng.f32()));
}
Some(tmp_cur)
}
Expand Down Expand Up @@ -313,6 +333,7 @@ mod tests {
fn test_exponential_max_delay_without_default_1() {
let mut exp = ExponentialBuilder {
jitter: false,
seed: Some(0x2fdb0020ffc7722b),
factor: 10_000_000_000_f32,
min_delay: Duration::from_secs(1),
max_delay: None,
Expand All @@ -330,6 +351,7 @@ mod tests {
fn test_exponential_max_delay_without_default_2() {
let mut exp = ExponentialBuilder {
jitter: true,
seed: Some(0x2fdb0020ffc7722b),
factor: 10_000_000_000_f32,
min_delay: Duration::from_secs(10_000_000_000),
max_delay: None,
Expand All @@ -347,6 +369,7 @@ mod tests {
fn test_exponential_max_delay_without_default_3() {
let mut exp = ExponentialBuilder {
jitter: false,
seed: Some(0x2fdb0020ffc7722b),
factor: 10_000_000_000_f32,
min_delay: Duration::from_secs(10_000_000_000),
max_delay: Some(Duration::from_secs(60_000_000_000)),
Expand Down
24 changes: 22 additions & 2 deletions backon/src/backoff/fibonacci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use crate::backoff::BackoffBuilder;
#[derive(Debug, Clone, Copy)]
pub struct FibonacciBuilder {
jitter: bool,
seed: Option<u64>,
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
Expand All @@ -45,6 +46,7 @@ impl Default for FibonacciBuilder {
fn default() -> Self {
Self {
jitter: false,
seed: None,
min_delay: Duration::from_secs(1),
max_delay: Some(Duration::from_secs(60)),
max_times: Some(3),
Expand All @@ -61,6 +63,12 @@ impl FibonacciBuilder {
self
}

/// Set the seed value for the jitter random number generator. If no seed is given, a random seed is used in std and default seed is used in no_std.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = Some(seed);
self
}

/// Set the minimum delay for the backoff.
pub fn with_min_delay(mut self, min_delay: Duration) -> Self {
self.min_delay = min_delay;
Expand Down Expand Up @@ -110,6 +118,17 @@ impl BackoffBuilder for FibonacciBuilder {
fn build(self) -> Self::Backoff {
FibonacciBackoff {
jitter: self.jitter,
rng: if let Some(seed) = self.seed {
fastrand::Rng::with_seed(seed)
} else {
#[cfg(feature = "std")]
let rng = fastrand::Rng::new();

#[cfg(not(feature = "std"))]
let rng = fastrand::Rng::with_seed(super::RANDOM_SEED);

rng
},
min_delay: self.min_delay,
max_delay: self.max_delay,
max_times: self.max_times,
Expand All @@ -136,6 +155,7 @@ impl BackoffBuilder for &FibonacciBuilder {
#[derive(Debug)]
pub struct FibonacciBackoff {
jitter: bool,
rng: fastrand::Rng,
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
Expand All @@ -162,7 +182,7 @@ impl Iterator for FibonacciBackoff {

// If jitter is enabled, add random jitter based on min delay.
if self.jitter {
next += self.min_delay.mul_f32(fastrand::f32());
next += self.min_delay.mul_f32(self.rng.f32());
}

Some(next)
Expand All @@ -181,7 +201,7 @@ impl Iterator for FibonacciBackoff {

// If jitter is enabled, add random jitter based on min delay.
if self.jitter {
next += self.min_delay.mul_f32(fastrand::f32());
next += self.min_delay.mul_f32(self.rng.f32());
}

Some(next)
Expand Down
4 changes: 4 additions & 0 deletions backon/src/backoff/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ pub use fibonacci::FibonacciBuilder;
mod exponential;
pub use exponential::ExponentialBackoff;
pub use exponential::ExponentialBuilder;

// Random seed value for no_std (the value is "backon" in hex)
#[cfg(not(feature = "std"))]
const RANDOM_SEED: u64 = 0x6261636b6f6e;
wackazong marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions backon/src/blocking_retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ where
}
#[cfg(test)]
mod tests {
extern crate alloc;

use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
Expand Down
2 changes: 2 additions & 0 deletions backon/src/blocking_retry_with_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ where

#[cfg(test)]
mod tests {
extern crate alloc;

use super::*;
use crate::ExponentialBuilder;
use alloc::string::ToString;
Expand Down
4 changes: 1 addition & 3 deletions backon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
//! |---------------------|--------------------|-------------|---------------|
//! | [`TokioSleeper`] | tokio-sleep | non-wasm32 | Yes |
//! | [`GlooTimersSleep`] | gloo-timers-sleep | wasm32 | Yes |
//! | [`StdSleeper`] | std-blocking-sleep | all | No |
//! | [`StdSleeper`] | std-blocking-sleep | std | No |
//!
//! ## Custom Sleeper
//!
Expand Down Expand Up @@ -153,8 +153,6 @@
#[cfg(feature = "std-blocking-sleep")]
extern crate std;

extern crate alloc;

mod backoff;
pub use backoff::*;

Expand Down
6 changes: 5 additions & 1 deletion backon/src/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,10 @@ where
}

#[cfg(test)]
#[cfg(any(feature = "tokio-sleep", feature = "gloo-timers-sleep"))]
#[cfg(any(feature = "tokio-sleep", feature = "gloo-timers-sleep",))]
mod default_sleeper_tests {
extern crate alloc;

use alloc::string::ToString;
use alloc::vec;
use alloc::vec::Vec;
Expand Down Expand Up @@ -428,6 +430,8 @@ mod default_sleeper_tests {

#[cfg(test)]
mod custom_sleeper_tests {
extern crate alloc;

use alloc::string::ToString;
use core::{future::ready, time::Duration};

Expand Down
4 changes: 3 additions & 1 deletion backon/src/retry_with_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,10 @@ where
}

#[cfg(test)]
#[cfg(any(feature = "tokio-sleep", feature = "gloo-timers-sleep"))]
#[cfg(any(feature = "tokio-sleep", feature = "gloo-timers-sleep",))]
mod tests {
extern crate alloc;

use alloc::string::ToString;
use anyhow::{anyhow, Result};
use core::time::Duration;
Expand Down
2 changes: 1 addition & 1 deletion backon/src/sleep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ impl<F: Fn(Duration) -> Fut + 'static, Fut: Future<Output = ()>> Sleeper for F {
/// The default implementation of `Sleeper` when no features are enabled.
///
/// It will fail to compile if a containing [`Retry`][crate::Retry] is `.await`ed without calling [`Retry::sleep`][crate::Retry::sleep] to provide a valid sleeper.
#[cfg(all(not(feature = "tokio-sleep"), not(feature = "gloo-timers-sleep")))]
#[cfg(all(not(feature = "tokio-sleep"), not(feature = "gloo-timers-sleep"),))]
pub type DefaultSleeper = PleaseEnableAFeatureOrProvideACustomSleeper;
/// The default implementation of `Sleeper` while feature `tokio-sleep` enabled.
///
Expand Down
Loading