governor/
jitter.rs

1use crate::nanos::Nanos;
2use core::ops::Add;
3use core::time::Duration;
4#[cfg(feature = "jitter")]
5use rand::distr::uniform::{SampleBorrow, SampleUniform, UniformInt, UniformSampler};
6#[cfg(feature = "jitter")]
7use rand::distr::{Distribution, Uniform};
8#[cfg(feature = "jitter")]
9use rand::{rng, Rng};
10
11#[cfg(feature = "std")]
12use std::time::Instant;
13
14/// An interval specification for deviating from the nominal wait time.
15///
16/// Jitter can be added to wait time `Duration`s to ensure that multiple tasks waiting on the same
17/// rate limit don't wake up at the same time and attempt to measure at the same time.
18///
19/// Methods on rate limiters that work asynchronously like
20/// [`DirectRateLimiter.until_ready_with_jitter`](struct.DirectRateLimiter.html#method.until_ready_with_jitter)
21/// exist to automatically apply jitter to wait periods, thereby reducing the chance of a
22/// thundering herd problem.
23///
24/// # Examples
25///
26/// Jitter can be added manually to a `Duration`:
27///
28/// ```rust
29/// # #[cfg(all(feature = "jitter", not(feature = "no_std")))]
30/// # fn main() {
31/// # use governor::Jitter;
32/// # use std::time::Duration;
33/// let reference = Duration::from_secs(24);
34/// let jitter = Jitter::new(Duration::from_secs(1), Duration::from_secs(1));
35/// let result = jitter + reference;
36/// assert!(result >= reference + Duration::from_secs(1));
37/// assert!(result < reference + Duration::from_secs(2))
38/// # }
39/// # #[cfg(not(all(feature = "jitter", not(feature = "no_std"))))]
40/// # fn main() {}
41/// ```
42///
43/// In a `std` build (the default), Jitter can also be added to an `Instant`:
44///
45/// ```rust
46/// # #[cfg(all(feature = "jitter", feature = "std"))]
47/// # fn main() {
48/// # use governor::Jitter;
49/// # use std::time::{Duration, Instant};
50/// let reference = Instant::now();
51/// let jitter = Jitter::new(Duration::from_secs(1), Duration::from_secs(1));
52/// let result = jitter + reference;
53/// assert!(result >= reference + Duration::from_secs(1));
54/// assert!(result < reference + Duration::from_secs(2))
55/// # }
56/// # #[cfg(any(not(feature = "jitter"), not(feature = "std")))] fn main() {}
57/// ```
58#[derive(Debug, PartialEq, Eq, Default, Clone, Copy)]
59pub struct Jitter {
60    min: Nanos,
61    max: Nanos,
62}
63
64impl Jitter {
65    #[cfg(feature = "std")]
66    /// The "empty" jitter interval - no jitter at all.
67    pub(crate) const NONE: Jitter = Jitter {
68        min: Nanos::new(0),
69        max: Nanos::new(0),
70    };
71
72    /// Constructs a new Jitter interval, waiting at most a duration of `max`.
73    ///
74    /// ```rust
75    /// # #[cfg(all(feature = "jitter", not(feature = "no_std")))]
76    /// # fn main() {
77    /// # use std::time::Duration;
78    /// # use governor::Jitter;
79    /// let jitter = Jitter::up_to(Duration::from_secs(20));
80    /// let now = Duration::from_secs(0);
81    /// assert!(jitter + now <= Duration::from_secs(20)); // always.
82    /// # }
83    /// # #[cfg(not(all(feature = "jitter", not(feature = "no_std"))))]
84    /// # fn main() {}
85    /// ```
86    #[cfg(any(all(feature = "jitter", not(feature = "no_std")), feature = "std"))]
87    pub fn up_to(max: Duration) -> Jitter {
88        Jitter {
89            min: Nanos::from(0),
90            max: max.into(),
91        }
92    }
93
94    /// Constructs a new Jitter interval, waiting at least `min` and at most `min+interval`.
95    #[cfg(any(all(feature = "jitter", not(feature = "no_std")), feature = "std"))]
96    pub fn new(min: Duration, interval: Duration) -> Jitter {
97        let min: Nanos = min.into();
98        let max: Nanos = min + Nanos::from(interval);
99        Jitter { min, max }
100    }
101
102    /// Returns a random amount of jitter within the configured interval.
103    #[cfg(feature = "jitter")]
104    pub(crate) fn get(&self) -> Nanos {
105        if self.min == self.max {
106            return self.min;
107        }
108        let uniform =
109            Uniform::new(self.min, self.max).expect("range is large enough for a distribution");
110        uniform.sample(&mut rng())
111    }
112
113    /// Returns a random amount of jitter within the configured interval.
114    #[cfg(not(feature = "jitter"))]
115    pub(crate) fn get(&self) -> Nanos {
116        self.min
117    }
118}
119
120/// A random distribution of nanoseconds
121#[cfg(feature = "jitter")]
122#[derive(Clone, Copy, Debug)]
123pub struct UniformJitter(UniformInt<u64>);
124
125#[cfg(feature = "jitter")]
126impl UniformSampler for UniformJitter {
127    type X = Nanos;
128
129    fn new<B1, B2>(low: B1, high: B2) -> Result<Self, rand::distr::uniform::Error>
130    where
131        B1: SampleBorrow<Self::X> + Sized,
132        B2: SampleBorrow<Self::X> + Sized,
133    {
134        Ok(UniformJitter(UniformInt::new(
135            low.borrow().as_u64(),
136            high.borrow().as_u64(),
137        )?))
138    }
139
140    fn new_inclusive<B1, B2>(low: B1, high: B2) -> Result<Self, rand::distr::uniform::Error>
141    where
142        B1: SampleBorrow<Self::X> + Sized,
143        B2: SampleBorrow<Self::X> + Sized,
144    {
145        Ok(UniformJitter(UniformInt::new(
146            low.borrow().as_u64(),
147            high.borrow().as_u64(),
148        )?))
149    }
150
151    fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Self::X {
152        Nanos::from(self.0.sample(rng))
153    }
154}
155
156#[cfg(feature = "jitter")]
157impl SampleUniform for Nanos {
158    type Sampler = UniformJitter;
159}
160
161impl Add<Duration> for Jitter {
162    type Output = Duration;
163
164    fn add(self, rhs: Duration) -> Duration {
165        let amount: Duration = self.get().into();
166        rhs + amount
167    }
168}
169
170impl Add<Nanos> for Jitter {
171    type Output = Nanos;
172
173    fn add(self, rhs: Nanos) -> Nanos {
174        rhs + self.get()
175    }
176}
177
178#[cfg(feature = "std")]
179impl Add<Instant> for Jitter {
180    type Output = Instant;
181
182    fn add(self, rhs: Instant) -> Instant {
183        let amount: Duration = self.get().into();
184        rhs + amount
185    }
186}
187
188#[cfg(all(feature = "jitter", not(feature = "no_std"), test))]
189mod test {
190    use super::*;
191
192    #[test]
193    fn jitter_impl_coverage() {
194        let basic = Jitter::up_to(Duration::from_secs(20));
195        let verbose = Jitter::new(Duration::from_secs(0), Duration::from_secs(20));
196        assert_eq!(basic, verbose);
197    }
198
199    #[test]
200    fn uniform_sampler_coverage() {
201        let low = Duration::from_secs(0);
202        let high = Duration::from_secs(20);
203        let sampler = UniformJitter::new_inclusive(Nanos::from(low), Nanos::from(high));
204        assert!(!format!("{sampler:?}").is_empty());
205        assert!(!format!("{:?}", sampler.clone()).is_empty());
206    }
207}