pgtask-core 0.2.0

Core types and state model for pgtask
Documentation
use std::time::Duration;

use rand::Rng as _;
use serde::{Deserialize, Serialize};

#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum RetryPolicy {
    Never,
    Fixed {
        delay: Duration,
    },
    Exponential {
        base_delay: Duration,
        factor: u32,
        max_delay: Duration,
    },
}

impl RetryPolicy {
    pub fn delay_for(self, attempt: u16) -> Option<Duration> {
        match self {
            Self::Never => None,
            Self::Fixed { delay } => Some(delay),
            Self::Exponential {
                base_delay,
                factor,
                max_delay,
            } => {
                let multiplier = factor.saturating_pow(u32::from(attempt.saturating_sub(1)));
                let ceiling = base_delay.saturating_mul(multiplier).min(max_delay);
                let ceiling_nanos = u64::try_from(ceiling.as_nanos()).unwrap_or(u64::MAX);
                Some(Duration::from_nanos(rand::rng().random_range(0..=ceiling_nanos)))
            }
        }
    }
}

impl Default for RetryPolicy {
    fn default() -> Self {
        Self::Exponential {
            base_delay: Duration::from_secs(1),
            factor: 2,
            max_delay: Duration::from_mins(1),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::time::Duration;

    use super::RetryPolicy;

    #[test]
    fn policies_calculate_bounded_delays() {
        assert_eq!(RetryPolicy::Never.delay_for(1), None);
        assert_eq!(
            RetryPolicy::Fixed {
                delay: Duration::from_secs(3)
            }
            .delay_for(10),
            Some(Duration::from_secs(3))
        );
        let exponential = RetryPolicy::Exponential {
            base_delay: Duration::from_secs(2),
            factor: 3,
            max_delay: Duration::from_secs(20),
        };
        for _ in 0..100 {
            assert!(exponential.delay_for(1).unwrap() <= Duration::from_secs(2));
            assert!(exponential.delay_for(2).unwrap() <= Duration::from_secs(6));
            assert!(exponential.delay_for(10).unwrap() <= Duration::from_secs(20));
        }
    }
}