shrike 0.1.0

AT Protocol library for Rust
Documentation
use std::time::Duration;

use rand::Rng;

/// Configures exponential backoff with full jitter for reconnection attempts.
pub struct BackoffPolicy {
    pub initial_delay: Duration,
    pub max_delay: Duration,
    pub multiplier: f64,
}

impl Default for BackoffPolicy {
    fn default() -> Self {
        BackoffPolicy {
            initial_delay: Duration::from_secs(1),
            max_delay: Duration::from_secs(30),
            multiplier: 2.0,
        }
    }
}

impl BackoffPolicy {
    /// Compute delay for a given attempt with full jitter.
    ///
    /// The base delay grows exponentially: `initial_delay * multiplier^attempt`,
    /// capped at `max_delay`. The returned value is a uniform random duration
    /// in `[0, capped_delay)`.
    pub fn delay(&self, attempt: u32) -> Duration {
        let base = self.initial_delay.as_secs_f64() * self.multiplier.powi(attempt as i32);
        let capped = base.min(self.max_delay.as_secs_f64());
        // Full jitter: uniform random in [0, capped)
        let jittered = rand::rng().random::<f64>() * capped;
        Duration::from_secs_f64(jittered)
    }
}

#[cfg(test)]
#[allow(
    clippy::unwrap_used,
    clippy::expect_used,
    clippy::panic,
    clippy::unreachable
)]
mod tests {
    use super::*;

    #[test]
    fn backoff_increases() {
        let policy = BackoffPolicy::default();
        // Delays should generally increase (modulo jitter).
        // Test the underlying math without jitter.
        let d0 = policy.initial_delay.as_secs_f64();
        let d1 = d0 * policy.multiplier;
        let d2 = d1 * policy.multiplier;
        assert!(d1 > d0);
        assert!(d2 > d1);
    }

    #[test]
    fn backoff_caps_at_max() {
        let policy = BackoffPolicy {
            initial_delay: Duration::from_secs(1),
            max_delay: Duration::from_secs(5),
            multiplier: 10.0,
        };
        // After a few attempts, delay should be capped.
        for _ in 0..100 {
            let delay = policy.delay(10);
            assert!(delay <= Duration::from_secs(5));
        }
    }

    #[test]
    fn backoff_jitter_produces_variety() {
        let policy = BackoffPolicy::default();
        let delays: Vec<Duration> = (0..20).map(|_| policy.delay(3)).collect();
        // With jitter, not all delays should be identical.
        let first = delays[0];
        assert!(
            delays.iter().any(|d| *d != first),
            "jitter should produce varied delays"
        );
    }
}