Skip to main content

qubit_retry/
jitter.rs

1/*******************************************************************************
2 *
3 *    Copyright (c) 2025 - 2026.
4 *    Haixing Hu, Qubit Co. Ltd.
5 *
6 *    All rights reserved.
7 *
8 ******************************************************************************/
9//! Jitter applied to retry delays.
10//!
11//! Jitter is applied after the base [`crate::Delay`] has been calculated. It
12//! helps callers avoid retry bursts when multiple tasks fail at the same time.
13
14use std::time::Duration;
15
16use rand::RngExt;
17
18/// Jitter applied after a base [`crate::Delay`] has been calculated.
19///
20/// The current implementation supports no jitter and symmetric factor-based
21/// jitter. Factor jitter keeps the lower bound at zero to avoid negative
22/// durations after randomization.
23#[derive(Debug, Clone, Copy, PartialEq)]
24pub enum Jitter {
25    /// No jitter.
26    None,
27
28    /// Symmetric relative jitter: `base +/- base * factor`.
29    Factor(f64),
30}
31
32impl Jitter {
33    /// Creates a no-jitter strategy.
34    ///
35    /// # Parameters
36    /// This function has no parameters.
37    ///
38    /// # Returns
39    /// A [`Jitter::None`] strategy.
40    ///
41    /// # Errors
42    /// This function does not return errors.
43    #[inline]
44    pub fn none() -> Self {
45        Self::None
46    }
47
48    /// Creates a symmetric relative jitter strategy.
49    ///
50    /// Validation requires `factor` to be finite and within `[0.0, 1.0]`.
51    ///
52    /// # Parameters
53    /// - `factor`: Relative jitter range. For example, `0.2` samples from
54    ///   `base +/- 20%`.
55    ///
56    /// # Returns
57    /// A [`Jitter::Factor`] strategy.
58    ///
59    /// # Errors
60    /// This constructor does not validate `factor`; use [`Jitter::validate`]
61    /// before applying values that come from configuration or user input.
62    #[inline]
63    pub fn factor(factor: f64) -> Self {
64        Self::Factor(factor)
65    }
66
67    /// Applies jitter to a base delay.
68    ///
69    /// A zero base delay is returned unchanged. Factor jitter samples a value
70    /// from the inclusive range `[-base * factor, base * factor]`.
71    ///
72    /// # Parameters
73    /// - `base`: Base delay calculated by [`crate::Delay`].
74    ///
75    /// # Returns
76    /// The jittered delay, never below zero.
77    ///
78    /// # Errors
79    /// This function does not return errors.
80    ///
81    /// # Panics
82    /// May panic if a [`Jitter::Factor`] value has not been validated and the
83    /// factor is non-finite, because the random range cannot be sampled.
84    pub fn apply(&self, base: Duration) -> Duration {
85        match self {
86            Self::None => base,
87            Self::Factor(factor) if *factor <= 0.0 || base.is_zero() => base,
88            Self::Factor(factor) => {
89                let base_nanos = base.as_nanos() as f64;
90                let span = base_nanos * factor;
91                let mut rng = rand::rng();
92                let jitter = rng.random_range(-span..=span);
93                Duration::from_nanos((base_nanos + jitter).max(0.0) as u64)
94            }
95        }
96    }
97
98    /// Validates jitter parameters.
99    ///
100    /// Returns a human-readable message when the factor is negative, greater
101    /// than `1.0`, NaN, or infinite.
102    ///
103    /// # Returns
104    /// `Ok(())` when the jitter configuration is usable.
105    ///
106    /// # Parameters
107    /// This method has no parameters.
108    ///
109    /// # Errors
110    /// Returns an error when the factor is negative, greater than `1.0`, NaN,
111    /// or infinite.
112    pub fn validate(&self) -> Result<(), String> {
113        match self {
114            Self::None => Ok(()),
115            Self::Factor(factor) => {
116                if !factor.is_finite() || *factor < 0.0 || *factor > 1.0 {
117                    Err("jitter factor must be finite and in range [0.0, 1.0]".to_string())
118                } else {
119                    Ok(())
120                }
121            }
122        }
123    }
124}
125
126impl Default for Jitter {
127    /// Creates the default jitter strategy.
128    ///
129    /// # Returns
130    /// [`Jitter::None`].
131    ///
132    /// # Parameters
133    /// This function has no parameters.
134    ///
135    /// # Errors
136    /// This function does not return errors.
137    #[inline]
138    fn default() -> Self {
139        Self::None
140    }
141}