Skip to main content

execution_policy/retry/
jitter.rs

1use std::time::Duration;
2
3/// Randomization applied to a backoff delay to de-correlate retriers.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Jitter {
6    /// No jitter — use the raw delay.
7    None,
8    /// Uniform random in `[0, base]` (AWS "full jitter").
9    Full,
10    /// `base/2 + uniform(0, base/2)` (AWS "equal jitter").
11    Equal,
12}
13
14impl Jitter {
15    /// Apply jitter to `base` using one random `u64`.
16    pub(crate) fn apply(&self, base: Duration, rng: u64) -> Duration {
17        if base.is_zero() {
18            return base;
19        }
20        let nanos = base.as_nanos() as u64;
21        // Fraction of `span` from the high bits of `rng`, in `[0, span)`.
22        let frac = |span: u64| -> u64 {
23            if span == 0 {
24                0
25            } else {
26                (((rng >> 11) as u128 * span as u128) >> 53) as u64
27            }
28        };
29        match self {
30            Jitter::None => base,
31            Jitter::Full => Duration::from_nanos(frac(nanos)),
32            Jitter::Equal => {
33                let half = nanos / 2;
34                Duration::from_nanos(half + frac(half))
35            }
36        }
37    }
38}
39
40#[cfg(test)]
41mod tests {
42    use super::*;
43
44    #[test]
45    fn none_is_identity() {
46        let d = Duration::from_millis(100);
47        assert_eq!(Jitter::None.apply(d, u64::MAX), d);
48    }
49
50    #[test]
51    fn full_is_within_bounds() {
52        let d = Duration::from_millis(100);
53        for rng in [0u64, 1, 12345, u64::MAX / 2, u64::MAX] {
54            let j = Jitter::Full.apply(d, rng);
55            assert!(j <= d, "full jitter {j:?} exceeded base {d:?}");
56        }
57    }
58
59    #[test]
60    fn equal_is_in_upper_half() {
61        let d = Duration::from_millis(100);
62        for rng in [0u64, 999, u64::MAX] {
63            let j = Jitter::Equal.apply(d, rng);
64            assert!(
65                j >= d / 2 && j <= d,
66                "equal jitter {j:?} out of [50ms,100ms]"
67            );
68        }
69    }
70
71    #[test]
72    fn zero_base_stays_zero() {
73        assert_eq!(Jitter::Full.apply(Duration::ZERO, 42), Duration::ZERO);
74    }
75}