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
9 changes: 5 additions & 4 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-blocking-sleep", "tokio-sleep", "gloo-timers-sleep", "std"]
wackazong marked this conversation as resolved.
Show resolved Hide resolved
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"]
std = ["fastrand/std"]
wackazong marked this conversation as resolved.
Show resolved Hide resolved

[dependencies]
fastrand = "2"
fastrand = { version = "2", default-features = false }

[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
tokio = { version = "1", optional = true }
Expand Down
31 changes: 29 additions & 2 deletions backon/src/backoff/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use core::time::Duration;

use crate::backoff::BackoffBuilder;

use super::Random;

/// ConstantBuilder is used to create a [`ConstantBackoff`], providing a steady delay with a fixed number of retries.
///
/// # Default
Expand Down Expand Up @@ -36,6 +38,7 @@ pub struct ConstantBuilder {
delay: Duration,
max_times: Option<usize>,
jitter: bool,
seed: u64,
wackazong marked this conversation as resolved.
Show resolved Hide resolved
}

impl Default for ConstantBuilder {
Expand All @@ -44,6 +47,7 @@ impl Default for ConstantBuilder {
delay: Duration::from_secs(1),
max_times: Some(3),
jitter: false,
seed: 0x2fdb0020ffc7722b,
}
}
}
Expand All @@ -61,14 +65,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.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}

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

attempts: 0,
jitter: self.jitter,
#[cfg(not(feature = "std"))]
wackazong marked this conversation as resolved.
Show resolved Hide resolved
seed: self.seed,
}
}
}
Expand All @@ -113,14 +125,29 @@ pub struct ConstantBackoff {

attempts: usize,
jitter: bool,
#[cfg(not(feature = "std"))]
wackazong marked this conversation as resolved.
Show resolved Hide resolved
seed: u64,
}

impl Random for ConstantBackoff {
#[cfg(not(feature = "std"))]
fn seed(&self) -> u64 {
self.seed
}

#[cfg(not(feature = "std"))]
fn set_seed(&mut self, seed: u64) {
self.seed = seed;
}
}

impl Iterator for ConstantBackoff {
type Item = Duration;

fn next(&mut self) -> Option<Self::Item> {
let jitter = self.jitter();
let delay = || match self.jitter {
true => self.delay + self.delay.mul_f32(fastrand::f32()),
true => self.delay + self.delay.mul_f32(jitter),
false => self.delay,
};
match self.max_times {
Expand Down
33 changes: 31 additions & 2 deletions backon/src/backoff/exponential.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use core::time::Duration;

use crate::backoff::BackoffBuilder;

use super::Random;

/// ExponentialBuilder is used to construct an [`ExponentialBackoff`] that offers delays with exponential retries.
///
/// # Default
Expand Down Expand Up @@ -41,6 +43,7 @@ pub struct ExponentialBuilder {
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
seed: u64,
}

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

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 +69,12 @@ impl ExponentialBuilder {
self
}

/// Set the seed value for the jitter random number generator.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = seed;
self
}

/// Set the factor for the backoff.
///
/// # Panics
Expand Down Expand Up @@ -126,6 +136,8 @@ impl BackoffBuilder for ExponentialBuilder {
fn build(self) -> Self::Backoff {
ExponentialBackoff {
jitter: self.jitter,
#[cfg(not(feature = "std"))]
wackazong marked this conversation as resolved.
Show resolved Hide resolved
seed: self.seed,
factor: self.factor,
min_delay: self.min_delay,
max_delay: self.max_delay,
Expand All @@ -152,6 +164,8 @@ impl BackoffBuilder for &ExponentialBuilder {
#[derive(Debug)]
pub struct ExponentialBackoff {
jitter: bool,
#[cfg(not(feature = "std"))]
seed: u64,
factor: f32,
min_delay: Duration,
max_delay: Option<Duration>,
Expand All @@ -161,6 +175,18 @@ pub struct ExponentialBackoff {
attempts: usize,
}

impl Random for ExponentialBackoff {
#[cfg(not(feature = "std"))]
fn seed(&self) -> u64 {
self.seed
}

#[cfg(not(feature = "std"))]
fn set_seed(&mut self, seed: u64) {
self.seed = seed;
}
}

impl Iterator for ExponentialBackoff {
type Item = Duration;

Expand Down Expand Up @@ -194,7 +220,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.jitter()));
}
Some(tmp_cur)
}
Expand Down Expand Up @@ -313,6 +339,7 @@ mod tests {
fn test_exponential_max_delay_without_default_1() {
let mut exp = ExponentialBuilder {
jitter: false,
seed: 0x2fdb0020ffc7722b,
factor: 10_000_000_000_f32,
min_delay: Duration::from_secs(1),
max_delay: None,
Expand All @@ -330,6 +357,7 @@ mod tests {
fn test_exponential_max_delay_without_default_2() {
let mut exp = ExponentialBuilder {
jitter: true,
seed: 0x2fdb0020ffc7722b,
factor: 10_000_000_000_f32,
min_delay: Duration::from_secs(10_000_000_000),
max_delay: None,
Expand All @@ -347,6 +375,7 @@ mod tests {
fn test_exponential_max_delay_without_default_3() {
let mut exp = ExponentialBuilder {
jitter: false,
seed: 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
30 changes: 28 additions & 2 deletions backon/src/backoff/fibonacci.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ use core::time::Duration;

use crate::backoff::BackoffBuilder;

use super::Random;

/// FibonacciBuilder is used to build a [`FibonacciBackoff`] which offers a delay with Fibonacci-based retries.
///
/// # Default
Expand Down Expand Up @@ -36,6 +38,7 @@ use crate::backoff::BackoffBuilder;
#[derive(Debug, Clone, Copy)]
pub struct FibonacciBuilder {
jitter: bool,
seed: u64,
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
Expand All @@ -45,6 +48,7 @@ impl Default for FibonacciBuilder {
fn default() -> Self {
Self {
jitter: false,
seed: 0x2fdb0020ffc7722b,
min_delay: Duration::from_secs(1),
max_delay: Some(Duration::from_secs(60)),
max_times: Some(3),
Expand All @@ -61,6 +65,12 @@ impl FibonacciBuilder {
self
}

/// Set the seed value for the jitter random number generator.
pub fn with_jitter_seed(mut self, seed: u64) -> Self {
self.seed = 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 +120,8 @@ impl BackoffBuilder for FibonacciBuilder {
fn build(self) -> Self::Backoff {
FibonacciBackoff {
jitter: self.jitter,
#[cfg(not(feature = "std"))]
seed: self.seed,
min_delay: self.min_delay,
max_delay: self.max_delay,
max_times: self.max_times,
Expand All @@ -136,6 +148,8 @@ impl BackoffBuilder for &FibonacciBuilder {
#[derive(Debug)]
pub struct FibonacciBackoff {
jitter: bool,
#[cfg(not(feature = "std"))]
seed: u64,
min_delay: Duration,
max_delay: Option<Duration>,
max_times: Option<usize>,
Expand All @@ -145,6 +159,18 @@ pub struct FibonacciBackoff {
attempts: usize,
}

impl Random for FibonacciBackoff {
#[cfg(not(feature = "std"))]
fn seed(&self) -> u64 {
self.seed
}

#[cfg(not(feature = "std"))]
fn set_seed(&mut self, seed: u64) {
self.seed = seed;
}
}

impl Iterator for FibonacciBackoff {
type Item = Duration;

Expand All @@ -162,7 +188,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.jitter());
}

Some(next)
Expand All @@ -181,7 +207,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.jitter());
}

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

trait Random {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I don't want to introduce a new trait for this. Maybe we can store a new fastrand::Rng in every FibonacciBackoff?

#[cfg(not(feature = "std"))]
fn seed(&self) -> u64;

#[cfg(not(feature = "std"))]
fn set_seed(&mut self, seed: u64);

fn jitter(&mut self) -> f32 {
#[cfg(feature = "std")]
return fastrand::f32();

#[cfg(not(feature = "std"))]
{
let result = fastrand::Rng::with_seed(self.seed()).f32();
// change the seed to get a new random number next time
self.set_seed(self.seed() ^ result.to_bits() as u64);
result
}
}
}
2 changes: 1 addition & 1 deletion 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
5 changes: 4 additions & 1 deletion backon/src/retry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +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 {
use alloc::string::ToString;
use alloc::vec;
Expand Down
5 changes: 4 additions & 1 deletion backon/src/retry_with_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +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 {
use alloc::string::ToString;
use anyhow::{anyhow, Result};
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