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}