etdl-core 0.1.0

ETDL runtime library: BranchMonitor, retry, SLA anomaly alerting, chaos injection, telemetry
Documentation
use std::future::Future;
use std::time::Duration;

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BackoffStrategy {
    Fixed,
    Exponential,
}

#[derive(Debug, Clone)]
pub struct RetryPolicy {
    pub max_attempts: u32,
    pub backoff_ms: u64,
    pub strategy: BackoffStrategy,
}

impl RetryPolicy {
    pub fn new(max_attempts: u32, backoff_ms: u64, strategy: BackoffStrategy) -> Self {
        RetryPolicy {
            max_attempts,
            backoff_ms,
            strategy,
        }
    }

    pub async fn execute<F, Fut, T, E>(
        &self,
        mut f: F,
        timeout: Duration,
    ) -> Result<T, E>
    where
        F: FnMut() -> Fut,
        Fut: Future<Output = Result<T, E>>,
        E: std::fmt::Debug,
    {
        let mut last_error = None;

        for attempt in 0..self.max_attempts {
            match tokio::time::timeout(timeout, f()).await {
                Ok(Ok(result)) => return Ok(result),
                Ok(Err(err)) => {
                    last_error = Some(err);
                }
                Err(_elapsed) => {
                    eprintln!("[etdl] retry attempt {} timed out", attempt + 1);
                }
            }

            if attempt < self.max_attempts - 1 {
                let delay_ms = match self.strategy {
                    BackoffStrategy::Fixed => self.backoff_ms,
                    BackoffStrategy::Exponential => {
                        self.backoff_ms * 2u64.pow(attempt)
                    }
                };
                tokio::time::sleep(Duration::from_millis(delay_ms)).await;
            }
        }

        if let Some(err) = last_error {
            Err(err)
        } else {
            panic!("retry exhausted all {} attempts without an error result", self.max_attempts)
        }
    }
}

impl Default for RetryPolicy {
    fn default() -> Self {
        RetryPolicy {
            max_attempts: 1,
            backoff_ms: 0,
            strategy: BackoffStrategy::Fixed,
        }
    }
}