Skip to main content

etdl_core/
retry.rs

1use std::future::Future;
2use std::time::Duration;
3
4#[derive(Debug, Clone, Copy, PartialEq)]
5pub enum BackoffStrategy {
6    Fixed,
7    Exponential,
8}
9
10#[derive(Debug, Clone)]
11pub struct RetryPolicy {
12    pub max_attempts: u32,
13    pub backoff_ms: u64,
14    pub strategy: BackoffStrategy,
15}
16
17impl RetryPolicy {
18    pub fn new(max_attempts: u32, backoff_ms: u64, strategy: BackoffStrategy) -> Self {
19        RetryPolicy {
20            max_attempts,
21            backoff_ms,
22            strategy,
23        }
24    }
25
26    pub async fn execute<F, Fut, T, E>(
27        &self,
28        mut f: F,
29        timeout: Duration,
30    ) -> Result<T, E>
31    where
32        F: FnMut() -> Fut,
33        Fut: Future<Output = Result<T, E>>,
34        E: std::fmt::Debug,
35    {
36        let mut last_error = None;
37
38        for attempt in 0..self.max_attempts {
39            match tokio::time::timeout(timeout, f()).await {
40                Ok(Ok(result)) => return Ok(result),
41                Ok(Err(err)) => {
42                    last_error = Some(err);
43                }
44                Err(_elapsed) => {
45                    eprintln!("[etdl] retry attempt {} timed out", attempt + 1);
46                }
47            }
48
49            if attempt < self.max_attempts - 1 {
50                let delay_ms = match self.strategy {
51                    BackoffStrategy::Fixed => self.backoff_ms,
52                    BackoffStrategy::Exponential => {
53                        self.backoff_ms * 2u64.pow(attempt)
54                    }
55                };
56                tokio::time::sleep(Duration::from_millis(delay_ms)).await;
57            }
58        }
59
60        if let Some(err) = last_error {
61            Err(err)
62        } else {
63            panic!("retry exhausted all {} attempts without an error result", self.max_attempts)
64        }
65    }
66}
67
68impl Default for RetryPolicy {
69    fn default() -> Self {
70        RetryPolicy {
71            max_attempts: 1,
72            backoff_ms: 0,
73            strategy: BackoffStrategy::Fixed,
74        }
75    }
76}