inference_remote_core/
backoff.rs1use 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
33pub 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
45fn 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 assert_eq!(compute_backoff(&p, 10), Duration::from_millis(2_000));
68 }
69}