mx-core 0.1.0

Core utilities for MultiversX Rust services.
Documentation
//! Exponential backoff calculation utilities.

use std::time::Duration;

/// Trait for backoff strategies.
pub trait Backoff: Send + Sync {
    /// Calculates the delay for the given attempt number (0-based).
    fn delay(&self, attempt: u32) -> Duration;

    /// Returns the maximum delay this backoff will produce.
    fn max_delay(&self) -> Duration;
}

/// Exponential backoff: `min(base * 2^attempt, max_delay)`.
#[derive(Debug, Clone)]
pub struct ExponentialBackoff {
    pub base_delay: Duration,
    pub max_delay: Duration,
    /// Maximum exponent to prevent overflow (default: 16).
    pub max_exponent: u32,
}

impl ExponentialBackoff {
    pub fn new(base_delay: Duration, max_delay: Duration) -> Self {
        Self {
            base_delay,
            max_delay,
            max_exponent: 16,
        }
    }

    pub fn default_settings() -> Self {
        Self::new(Duration::from_millis(100), Duration::from_secs(30))
    }

    pub fn with_max_exponent(mut self, max_exponent: u32) -> Self {
        self.max_exponent = max_exponent;
        self
    }
}

impl Default for ExponentialBackoff {
    fn default() -> Self {
        Self::default_settings()
    }
}

impl Backoff for ExponentialBackoff {
    fn delay(&self, attempt: u32) -> Duration {
        let exponent = attempt.min(self.max_exponent);
        let multiplier = 1u64.checked_shl(exponent).unwrap_or(u64::MAX);
        let delay_nanos = self
            .base_delay
            .as_nanos()
            .saturating_mul(u128::from(multiplier));
        let delay = Duration::from_nanos(delay_nanos.min(u128::from(u64::MAX)) as u64);
        delay.min(self.max_delay)
    }

    fn max_delay(&self) -> Duration {
        self.max_delay
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_exponential_sequence() {
        let b = ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(30));
        assert_eq!(b.delay(0), Duration::from_millis(100));
        assert_eq!(b.delay(1), Duration::from_millis(200));
        assert_eq!(b.delay(2), Duration::from_millis(400));
        assert_eq!(b.delay(3), Duration::from_millis(800));
    }

    #[test]
    fn test_capped_at_max() {
        let b = ExponentialBackoff::new(Duration::from_millis(100), Duration::from_secs(1));
        assert_eq!(b.delay(10), Duration::from_secs(1));
        assert_eq!(b.delay(20), Duration::from_secs(1));
    }

    #[test]
    fn test_overflow_protection() {
        let b = ExponentialBackoff::new(Duration::from_secs(1), Duration::from_secs(3600));
        let delay = b.delay(100);
        assert!(delay <= Duration::from_secs(3600));
    }
}