Skip to main content

inference_remote_core/
backoff.rs

1//! Exponential backoff + jitter, mirroring rakka's
2//! `pattern::backoff::BackoffOptions` shape but specialised for the
3//! per-request retry loop inside `RemoteWorkerActor`.
4//!
5//! For supervisor-level "actor restarted N times" backoff we still use
6//! `rakka_core::pattern::backoff::BackoffOptions` directly. This module
7//! is the per-call analogue.
8
9use std::time::Duration;
10
11use inference_core::deployment::RetryPolicy;
12use inference_core::runtime::JitterKind;
13
14#[derive(Debug, Clone)]
15pub struct BackoffPolicy {
16    pub initial: Duration,
17    pub max: Duration,
18    pub multiplier: f64,
19    pub jitter: JitterKind,
20}
21
22impl From<&RetryPolicy> for BackoffPolicy {
23    fn from(p: &RetryPolicy) -> Self {
24        Self {
25            initial: p.initial_backoff,
26            max: p.max_backoff,
27            multiplier: p.backoff_multiplier,
28            jitter: p.jitter,
29        }
30    }
31}
32
33/// Compute the next backoff. `attempt` is 0-indexed.
34pub fn compute_backoff(policy: &BackoffPolicy, attempt: u32) -> Duration {
35    let base_ms = policy.initial.as_millis() as f64 * policy.multiplier.powi(attempt as i32);
36    let capped = base_ms.min(policy.max.as_millis() as f64);
37    let with_jitter = match policy.jitter {
38        JitterKind::None => capped,
39        JitterKind::Equal => capped * 0.5 + capped * pseudo_random_01(attempt) * 0.5,
40        JitterKind::Full => capped * pseudo_random_01(attempt),
41    };
42    Duration::from_millis(with_jitter.max(0.0) as u64)
43}
44
45/// Deterministic pseudo-randomness — tests don't want real entropy.
46/// Same idiom rakka uses in `pattern::backoff`.
47fn pseudo_random_01(seed: u32) -> f64 {
48    ((seed.wrapping_mul(2_654_435_761)) % 10_000) as f64 / 10_000.0
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54
55    #[test]
56    fn backoff_grows_and_caps() {
57        let p = BackoffPolicy {
58            initial: Duration::from_millis(100),
59            max: Duration::from_millis(2_000),
60            multiplier: 2.0,
61            jitter: JitterKind::None,
62        };
63        assert_eq!(compute_backoff(&p, 0), Duration::from_millis(100));
64        assert_eq!(compute_backoff(&p, 1), Duration::from_millis(200));
65        assert_eq!(compute_backoff(&p, 2), Duration::from_millis(400));
66        // Capped:
67        assert_eq!(compute_backoff(&p, 10), Duration::from_millis(2_000));
68    }
69}