Skip to main content

solti_model/domain/policy/
jitter.rs

1//! # Jitter strategy.
2//!
3//! [`JitterPolicy`] adds randomness to backoff delays to prevent thundering-herd effects.
4
5use serde::{Deserialize, Serialize};
6use std::str::FromStr;
7
8use crate::error::{ModelError, ModelResult};
9
10/// Controls how random jitter is applied to backoff delays.
11///
12/// Jitter distributes retries over time, preventing synchronized "retry storms" when many tasks fail simultaneously.
13///
14/// | Variant        | Delay range                  | Collision resistance |
15/// |----------------|------------------------------|----------------------|
16/// | `None`         | exactly `base`               | none (deterministic) |
17/// | `Full`         | uniform `[0, base]`          | highest              |
18/// | `Equal`        | `base/2 ± rand(base/2)`      | moderate             |
19/// | `Decorrelated` | `min(max, rand(base * 3))`   | high                 |
20///
21/// The exact math is implemented in the backoff subsystem; this enum only selects the strategy.
22#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase")]
24#[non_exhaustive]
25pub enum JitterPolicy {
26    /// Full jitter: delay is uniformly sampled from `[0, base]`.
27    #[default]
28    Full,
29    /// No randomness applied. Backoff durations remain fixed.
30    None,
31    /// Equal jitter: delay is sampled around the midpoint (`base / 2`), providing a balance between stability and randomness.
32    Equal,
33    /// Decorrelated jitter: delay is sampled from `min(max, rand(base * 3))`.
34    Decorrelated,
35}
36
37impl FromStr for JitterPolicy {
38    type Err = ModelError;
39    fn from_str(s: &str) -> ModelResult<Self> {
40        let s = s.trim();
41        if s.is_empty() || s.eq_ignore_ascii_case("full") || s.eq_ignore_ascii_case("default") {
42            Ok(JitterPolicy::Full)
43        } else if s.eq_ignore_ascii_case("none") {
44            Ok(JitterPolicy::None)
45        } else if s.eq_ignore_ascii_case("equal") {
46            Ok(JitterPolicy::Equal)
47        } else if s.eq_ignore_ascii_case("decorrelated") {
48            Ok(JitterPolicy::Decorrelated)
49        } else {
50            Err(ModelError::UnknownJitter(s.to_string()))
51        }
52    }
53}