firebase_rs_sdk/util/
backoff.rs

1use rand::Rng;
2
3pub const DEFAULT_INTERVAL_MILLIS: u64 = 1_000;
4pub const DEFAULT_BACKOFF_FACTOR: f64 = 2.0;
5pub const MAX_BACKOFF_MILLIS: u64 = 4 * 60 * 60 * 1_000;
6pub const RANDOM_FACTOR: f64 = 0.5;
7
8#[derive(Debug, Clone, Copy)]
9pub struct BackoffConfig {
10    pub interval_millis: u64,
11    pub backoff_factor: f64,
12}
13
14impl Default for BackoffConfig {
15    fn default() -> Self {
16        Self {
17            interval_millis: DEFAULT_INTERVAL_MILLIS,
18            backoff_factor: DEFAULT_BACKOFF_FACTOR,
19        }
20    }
21}
22
23pub fn calculate_backoff_millis(backoff_count: u32) -> u64 {
24    calculate_backoff_with_rng(
25        backoff_count,
26        BackoffConfig::default(),
27        &mut rand::thread_rng(),
28    )
29}
30
31fn calculate_backoff_with_rng<R: Rng + ?Sized>(
32    backoff_count: u32,
33    config: BackoffConfig,
34    rng: &mut R,
35) -> u64 {
36    let base = (config.interval_millis as f64) * config.backoff_factor.powi(backoff_count as i32);
37    let jitter = RANDOM_FACTOR * base * rng.gen_range(-1.0..=1.0);
38    let value = (base + jitter)
39        .round()
40        .clamp(0.0, MAX_BACKOFF_MILLIS as f64);
41    value as u64
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47    use rand::rngs::StdRng;
48    use rand::SeedableRng;
49
50    #[test]
51    fn deterministic_with_seeded_rng() {
52        let mut rng = StdRng::seed_from_u64(42);
53        let value = calculate_backoff_with_rng(3, BackoffConfig::default(), &mut rng);
54        assert!(value > 0);
55        assert!(value <= MAX_BACKOFF_MILLIS);
56    }
57
58    #[test]
59    fn backoff_grows_with_count() {
60        let mut rng = StdRng::seed_from_u64(1);
61        let first = calculate_backoff_with_rng(0, BackoffConfig::default(), &mut rng);
62        let mut rng = StdRng::seed_from_u64(1);
63        let later = calculate_backoff_with_rng(4, BackoffConfig::default(), &mut rng);
64        assert!(later >= first);
65    }
66}