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}