diff --git a/backon/Cargo.toml b/backon/Cargo.toml index de9d2c1..d6bd83e 100644 --- a/backon/Cargo.toml +++ b/backon/Cargo.toml @@ -20,13 +20,14 @@ 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 = ["gloo-timers/futures"] tokio-sleep = ["tokio/time"] [dependencies] -fastrand = "2" +fastrand = { version = "2", default-features = false } [target.'cfg(not(target_arch = "wasm32"))'.dependencies] tokio = { version = "1", optional = true } diff --git a/backon/src/backoff/constant.rs b/backon/src/backoff/constant.rs index 117b156..277abd6 100644 --- a/backon/src/backoff/constant.rs +++ b/backon/src/backoff/constant.rs @@ -36,6 +36,7 @@ pub struct ConstantBuilder { delay: Duration, max_times: Option, jitter: bool, + seed: Option, } impl Default for ConstantBuilder { @@ -44,6 +45,7 @@ impl Default for ConstantBuilder { delay: Duration::from_secs(1), max_times: Some(3), jitter: false, + seed: None, } } } @@ -61,7 +63,7 @@ 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 { @@ -69,6 +71,12 @@ impl ConstantBuilder { 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. @@ -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 + }, } } } @@ -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 { - 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 { diff --git a/backon/src/backoff/exponential.rs b/backon/src/backoff/exponential.rs index 44729b8..202c314 100644 --- a/backon/src/backoff/exponential.rs +++ b/backon/src/backoff/exponential.rs @@ -41,6 +41,7 @@ pub struct ExponentialBuilder { min_delay: Duration, max_delay: Option, max_times: Option, + seed: Option, } impl Default for ExponentialBuilder { @@ -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. @@ -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 @@ -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, @@ -152,6 +171,7 @@ impl BackoffBuilder for &ExponentialBuilder { #[derive(Debug)] pub struct ExponentialBackoff { jitter: bool, + rng: fastrand::Rng, factor: f32, min_delay: Duration, max_delay: Option, @@ -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) } @@ -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, @@ -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, @@ -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)), diff --git a/backon/src/backoff/fibonacci.rs b/backon/src/backoff/fibonacci.rs index feae5d2..27bf5ae 100644 --- a/backon/src/backoff/fibonacci.rs +++ b/backon/src/backoff/fibonacci.rs @@ -36,6 +36,7 @@ use crate::backoff::BackoffBuilder; #[derive(Debug, Clone, Copy)] pub struct FibonacciBuilder { jitter: bool, + seed: Option, min_delay: Duration, max_delay: Option, max_times: Option, @@ -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), @@ -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; @@ -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, @@ -136,6 +155,7 @@ impl BackoffBuilder for &FibonacciBuilder { #[derive(Debug)] pub struct FibonacciBackoff { jitter: bool, + rng: fastrand::Rng, min_delay: Duration, max_delay: Option, max_times: Option, @@ -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) @@ -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) diff --git a/backon/src/backoff/mod.rs b/backon/src/backoff/mod.rs index 14d0b19..3f9fbdc 100644 --- a/backon/src/backoff/mod.rs +++ b/backon/src/backoff/mod.rs @@ -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; diff --git a/backon/src/blocking_retry.rs b/backon/src/blocking_retry.rs index 8194f5e..c712e4a 100644 --- a/backon/src/blocking_retry.rs +++ b/backon/src/blocking_retry.rs @@ -255,6 +255,8 @@ where } #[cfg(test)] mod tests { + extern crate alloc; + use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; diff --git a/backon/src/blocking_retry_with_context.rs b/backon/src/blocking_retry_with_context.rs index e66fa46..aca14a8 100644 --- a/backon/src/blocking_retry_with_context.rs +++ b/backon/src/blocking_retry_with_context.rs @@ -182,6 +182,8 @@ where #[cfg(test)] mod tests { + extern crate alloc; + use super::*; use crate::ExponentialBuilder; use alloc::string::ToString; diff --git a/backon/src/lib.rs b/backon/src/lib.rs index 2cac7c7..d6e4366 100644 --- a/backon/src/lib.rs +++ b/backon/src/lib.rs @@ -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 //! @@ -153,8 +153,6 @@ #[cfg(feature = "std-blocking-sleep")] extern crate std; -extern crate alloc; - mod backoff; pub use backoff::*; diff --git a/backon/src/retry.rs b/backon/src/retry.rs index a841018..5fedc36 100644 --- a/backon/src/retry.rs +++ b/backon/src/retry.rs @@ -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; @@ -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}; diff --git a/backon/src/retry_with_context.rs b/backon/src/retry_with_context.rs index c82ab43..664f792 100644 --- a/backon/src/retry_with_context.rs +++ b/backon/src/retry_with_context.rs @@ -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; diff --git a/backon/src/sleep.rs b/backon/src/sleep.rs index 4a5ab46..71dab5c 100644 --- a/backon/src/sleep.rs +++ b/backon/src/sleep.rs @@ -36,7 +36,7 @@ impl Fut + 'static, Fut: Future> 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. ///