iroh_relay/
ping_tracker.rs1use n0_future::time::{self, Duration, Instant};
2use tracing::debug;
3
4const PING_TIMEOUT: Duration = Duration::from_secs(5);
6
7const MIN_HEALTH_CHECK_TIMEOUT: Duration = Duration::from_millis(500);
9
10#[derive(Debug)]
14pub struct PingTracker {
15 inner: Option<PingInner>,
16 max_timeout: Duration,
17 last_rtt: Option<Duration>,
19}
20
21#[derive(Debug)]
22struct PingInner {
23 data: [u8; 8],
24 deadline: Instant,
25 sent_at: Instant,
26}
27
28impl Default for PingTracker {
29 fn default() -> Self {
30 Self::new(PING_TIMEOUT)
31 }
32}
33
34impl PingTracker {
35 pub fn new(max_timeout: Duration) -> Self {
37 Self {
38 inner: None,
39 max_timeout,
40 last_rtt: None,
41 }
42 }
43
44 pub fn max_timeout(&self) -> Duration {
46 self.max_timeout
47 }
48
49 pub fn new_ping(&mut self) -> [u8; 8] {
51 let timeout = self.ping_timeout();
52 self.new_ping_with_timeout(timeout)
53 }
54
55 pub fn new_ping_with_timeout(&mut self, timeout: Duration) -> [u8; 8] {
57 let ping_data = rand::random();
58 let now = Instant::now();
59 debug!(data = ?ping_data, "Sending ping to relay server.");
60 self.inner = Some(PingInner {
61 data: ping_data,
62 deadline: now + timeout,
63 sent_at: now,
64 });
65 ping_data
66 }
67
68 pub fn pong_received(&mut self, data: [u8; 8]) {
73 if let Some(inner) = &self.inner
74 && inner.data == data
75 {
76 let rtt = inner.sent_at.elapsed();
77 debug!(?data, ?rtt, "Pong received from relay server");
78 self.last_rtt = Some(rtt);
79 self.inner = None;
80 }
81 }
82
83 pub fn ping_timeout(&self) -> Duration {
88 self.last_rtt
89 .map(|rtt| (rtt * 3).clamp(MIN_HEALTH_CHECK_TIMEOUT, self.max_timeout))
90 .unwrap_or(self.max_timeout)
91 }
92
93 pub async fn timeout(&mut self) {
97 match self.inner {
98 Some(PingInner { deadline, data, .. }) => {
99 time::sleep_until(deadline).await;
100 debug!(?data, "Ping timeout.");
101 self.inner = None;
102 }
103 None => std::future::pending().await,
104 }
105 }
106}