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}