governor/
jitter.rs

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