Skip to main content

rash/
backoff.rs

1//! Restart backoff — a port of autossh's `grace_time()` (autossh.c:1115-1154).
2//!
3//! Restarts that keep failing quickly are spaced further and further apart, up
4//! to the poll interval. A session that stays up long enough resets the count.
5//!
6//! The arithmetic is deliberately kept in `f64` with a truncating cast, exactly
7//! as the C computes it, so the two agree second for second.
8
9use std::time::Duration;
10
11/// `N_FAST_TRIES` (autossh.c:110): this many quick retries before any delay.
12const FAST_TRIES: u32 = 5;
13
14#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
15pub struct Backoff {
16    tries: u32,
17}
18
19impl Backoff {
20    /// Record a restart whose predecessor ran for `uptime`, and say how long to
21    /// wait before starting the next one.
22    ///
23    /// For the very first start there is no predecessor; pass `Duration::MAX`.
24    pub fn next_delay(&mut self, uptime: Duration, poll: Duration) -> Duration {
25        let poll_secs = poll.as_secs();
26
27        // Stay up for a tenth of the poll interval — at least 10s — and the
28        // count resets. Integer division, as in the C.
29        let min_time = (poll_secs / 10).max(10);
30        if uptime.as_secs() >= min_time {
31            self.tries = 0;
32        } else {
33            self.tries += 1;
34        }
35
36        if self.tries <= FAST_TRIES {
37            return Duration::ZERO;
38        }
39
40        // interval = (poll / 100) * t^2 / 3, capped at poll. The C truncates to
41        // int before comparing against the cap, so this does too.
42        let t = f64::from(self.tries - FAST_TRIES);
43        let n = ((poll_secs as f64 / 100.0) * (t * (t / 3.0))) as u64;
44        Duration::from_secs(n.min(poll_secs))
45    }
46
47    /// How many quick restarts in a row have been seen.
48    pub fn tries(&self) -> u32 {
49        self.tries
50    }
51}