dag_executor/advanced/
retry.rs1use rand::Rng;
4use std::time::Duration;
5
6#[derive(Debug, Clone, Copy)]
8pub enum Backoff {
9 Fixed(Duration),
11 Linear {
13 base: Duration,
15 max: Duration,
17 },
18 Exponential {
20 base: Duration,
22 factor: f64,
24 max: Duration,
26 },
27}
28
29impl Backoff {
30 pub fn delay(&self, attempt: u32) -> Duration {
33 let attempt = attempt.max(1);
34 match *self {
35 Backoff::Fixed(d) => d,
36 Backoff::Linear { base, max } => base.checked_mul(attempt).unwrap_or(max).min(max),
37 Backoff::Exponential { base, factor, max } => {
38 let mult = factor.powi((attempt - 1) as i32);
39 let secs = base.as_secs_f64() * mult;
40 let capped = secs.min(max.as_secs_f64());
41 Duration::from_secs_f64(capped.max(0.0))
42 }
43 }
44 }
45}
46
47#[derive(Debug, Clone, Copy)]
50pub struct RetryPolicy {
51 pub max_attempts: u32,
53 pub backoff: Backoff,
55 pub jitter: bool,
57}
58
59impl RetryPolicy {
60 pub fn none() -> Self {
62 RetryPolicy {
63 max_attempts: 1,
64 backoff: Backoff::Fixed(Duration::ZERO),
65 jitter: false,
66 }
67 }
68
69 pub fn fixed(max_attempts: u32, delay: Duration) -> Self {
71 RetryPolicy {
72 max_attempts,
73 backoff: Backoff::Fixed(delay),
74 jitter: false,
75 }
76 }
77
78 pub fn exponential(max_attempts: u32, base: Duration) -> Self {
80 RetryPolicy {
81 max_attempts,
82 backoff: Backoff::Exponential {
83 base,
84 factor: 2.0,
85 max: Duration::from_secs(60),
86 },
87 jitter: true,
88 }
89 }
90
91 pub fn should_retry(&self, attempts_made: u32) -> bool {
93 attempts_made < self.max_attempts
94 }
95
96 pub fn delay_for(&self, attempts_made: u32) -> Duration {
99 let base = self.backoff.delay(attempts_made);
100 if !self.jitter || base.is_zero() {
101 return base;
102 }
103 let secs = base.as_secs_f64();
104 let factor = rand::thread_rng().gen_range(0.75..=1.25);
105 Duration::from_secs_f64(secs * factor)
106 }
107}
108
109impl Default for RetryPolicy {
110 fn default() -> Self {
111 RetryPolicy::exponential(3, Duration::from_millis(100))
112 }
113}