Skip to content

Commit

Permalink
feat: Add std support for no-std without global rand seed (#169)
Browse files Browse the repository at this point in the history
Split #168 into two PRs as
requested, this is the one for no_std support.

---------

Co-authored-by: Xuanwo <[email protected]>
  • Loading branch information
wackazong and Xuanwo authored Dec 17, 2024
1 parent c574c61 commit f7e3b97
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 15 deletions.
5 changes: 3 additions & 2 deletions backon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
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,
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;
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

0 comments on commit f7e3b97

Please sign in to comment.