watchctl 0.1.0

Process supervisor with wait, watch, and retry phases
use crate::config::RetryConfig;
use std::process::ExitStatus;
use std::time::Duration;
use tokio::time::sleep;
use tracing::info;

const MAX_BACKOFF_DELAY: Duration = Duration::from_secs(300);

pub struct RetryState {
    pub attempts_remaining: u32,
    pub current_delay: Duration,
}

impl RetryState {
    pub fn new(config: &RetryConfig) -> Self {
        Self {
            attempts_remaining: config.times,
            current_delay: config.delay,
        }
    }

    pub fn should_retry(&self, config: &RetryConfig, exit_status: Option<ExitStatus>) -> bool {
        if self.attempts_remaining == 0 {
            return false;
        }

        let code = exit_status.and_then(|s| s.code());

        match (&config.exit_codes, code) {
            (None, Some(0)) => false,
            (None, _) => true,
            (Some(codes), Some(c)) => codes.contains(&c),
            (Some(_), None) => true,
        }
    }

    pub async fn wait_before_retry(&mut self, config: &RetryConfig) {
        info!(
            "retrying in {:?} ({} attempts remaining)",
            self.current_delay, self.attempts_remaining
        );
        sleep(self.current_delay).await;

        self.attempts_remaining -= 1;

        if config.backoff {
            self.current_delay = self.current_delay.saturating_mul(2).min(MAX_BACKOFF_DELAY);
        }
    }
}