Skip to main content

protocol/
liveness.rs

1//! Application-level liveness (spec: Liveness detection).
2//!
3//! Over `adb forward` the TCP hop ends at the local adb server, so TCP
4//! keepalive proves nothing about the agent. Instead each side pings after
5//! [`crate::session::PING_INTERVAL`] without hearing from the other and
6//! declares it dead after [`crate::session::PEER_TIMEOUT`].
7
8use std::time::{Duration, Instant};
9
10/// What a [`Liveness`] check asks for.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Alive {
13    /// Nothing to do.
14    Yes,
15    /// Send a ping now.
16    Ping,
17    /// Nothing arrived for this long: the other side is dead.
18    Dead(Duration),
19}
20
21/// Silence tracking for one side of a connection.
22#[derive(Clone, Debug)]
23pub struct Liveness {
24    interval: Duration,
25    timeout: Duration,
26    last_heard: Instant,
27    last_ping: Option<Instant>,
28}
29
30impl Liveness {
31    /// Starts counting silence at `now`.
32    pub fn new(interval: Duration, timeout: Duration, now: Instant) -> Liveness {
33        Liveness {
34            interval,
35            timeout,
36            last_heard: now,
37            last_ping: None,
38        }
39    }
40
41    /// Something arrived from the other side.
42    pub fn heard(&mut self, now: Instant) {
43        self.last_heard = self.last_heard.max(now);
44    }
45
46    /// Checks the silence at `now`; pings only when `may_ping`.
47    pub fn check(&mut self, now: Instant, may_ping: bool) -> Alive {
48        let silence = now.saturating_duration_since(self.last_heard);
49        if silence >= self.timeout {
50            return Alive::Dead(silence);
51        }
52        if may_ping && silence >= self.interval && now >= self.next_ping() {
53            self.last_ping = Some(now);
54            return Alive::Ping;
55        }
56        Alive::Yes
57    }
58
59    /// When [`Liveness::check`] next has something to do.
60    pub fn next_check(&self, may_ping: bool) -> Instant {
61        let dead_at = self.last_heard + self.timeout;
62        if may_ping {
63            dead_at.min(self.next_ping())
64        } else {
65            dead_at
66        }
67    }
68
69    /// One interval after the later of the last ping and the last sign of life.
70    fn next_ping(&self) -> Instant {
71        let since = match self.last_ping {
72            Some(ping) if ping > self.last_heard => ping,
73            _ => self.last_heard,
74        };
75        since + self.interval
76    }
77}