Skip to main content

nifty_download/
retry.rs

1//! Trying a connection again after it fails in a way that may pass, waiting longer after
2//! each failure in a row.
3
4use std::time::Duration;
5
6/// How long to wait before the first retry; each one after waits twice as long.
7const FIRST_DELAY: Duration = Duration::from_secs(1);
8/// The longest wait, so a download that keeps failing still tries now and then.
9const LONGEST_DELAY: Duration = Duration::from_secs(30);
10
11/// How many times in a row a connection is tried again when the call asks for `asked`;
12/// any number below zero asks for none.
13pub(crate) fn retry_count(asked: i64) -> u32 {
14    u32::try_from(asked.max(0)).unwrap_or(u32::MAX)
15}
16
17/// How long to wait after `failures` failures in a row, the first being `1`.
18pub(crate) fn delay(failures: u32) -> Duration {
19    let doublings = failures.saturating_sub(1);
20    FIRST_DELAY
21        .saturating_mul(2_u32.saturating_pow(doublings))
22        .min(LONGEST_DELAY)
23}
24
25#[cfg(test)]
26mod tests {
27    use super::*;
28
29    #[test]
30    fn waits_twice_as_long_after_each_failure_up_to_a_limit() {
31        let waits: Vec<_> = (1..=7).map(|failures| delay(failures).as_secs()).collect();
32        assert_eq!(waits, [1, 2, 4, 8, 16, 30, 30]);
33        assert_eq!(delay(u32::MAX), LONGEST_DELAY);
34    }
35
36    #[test]
37    fn retries_at_least_zero_times() {
38        assert_eq!(retry_count(-1), 0);
39        assert_eq!(retry_count(3), 3);
40        assert_eq!(retry_count(i64::MAX), u32::MAX);
41    }
42}