diff options
| author | tv <tv@krebsco.de> | 2026-10-28 03:17:29 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-28 03:17:29 +0100 |
| commit | 30c2ca8537fbc2874ec6e14cdd1ae5477c95f099 (patch) | |
| tree | ecdb99d28bed6c5d9ed19637c1e725c9b2a532ca /src/transport/paths.rs | |
| parent | 614e57396d11700721660d54f3d90a4f01cfae7d (diff) | |
measure RTT on single link paths, too
Diffstat (limited to 'src/transport/paths.rs')
| -rw-r--r-- | src/transport/paths.rs | 51 |
1 files changed, 38 insertions, 13 deletions
diff --git a/src/transport/paths.rs b/src/transport/paths.rs index dda2d87..6d4b3a3 100644 --- a/src/transport/paths.rs +++ b/src/transport/paths.rs @@ -15,8 +15,9 @@ //! next address, the message encoded for its transport: when one transport fails, the //! next carries on within the same send. //! -//! Paths measures the addresses of such nodes itself: every `PROBE_EVERY`, each address -//! it had a choice of within `ACTIVE_FOR` gets a PING with a random nonce, which the +//! Paths measures the addresses of such nodes itself (and every path the node's +//! keepalive PINGs go over, below): every `PROBE_EVERY`, each address it had a choice of +//! within `ACTIVE_FOR` gets a PING with a random nonce, which the //! other side's paths answers at once with a PONG, over the same path. Answers give each //! path's round-trip time (smoothed); `UNANSWERED_DOWN` PINGs in a row unanswered, or a //! send that failed within `DOWN_FOR`, make a path down. `rank` puts paths that are up @@ -266,13 +267,19 @@ impl Table { } /// The node sent a PING with `nonce` to `address` (its keepalive): the probe of that - /// path, if paths measures it. + /// path, which its PONG measures, even if it is a peer's only path (then it isn't + /// pinged otherwise: `due` pings only paths there was a choice of). fn pinged(&mut self, address: &str, nonce: u64, now: Instant) { - if let Some(path) = self.paths.get_mut(address) { - path.probe = Some((nonce, now)); - path.last_probe = Some(now); - self.counts.probes_sent += 1; + if self.paths.len() >= MAX_PATHS && !self.paths.contains_key(address) { + self.forget_unused(now); + if self.paths.len() >= MAX_PATHS { + return; + } } + let path = self.paths.entry(address.to_owned()).or_default(); + path.probe = Some((nonce, now)); + path.last_probe = Some(now); + self.counts.probes_sent += 1; } /// A PONG with `nonce` from `address`: the round trip, if it answers our PING out. @@ -297,12 +304,14 @@ impl Table { path.rtt } - /// Forgets the paths not used for twice `ACTIVE_FOR`, and choices among them. + /// Forgets the paths neither used nor pinged for twice `ACTIVE_FOR`, and choices + /// among them. fn forget_unused(&mut self, now: Instant) { - self.paths.retain(|_, path| { - path.used - .is_some_and(|used| now.saturating_duration_since(used) < ACTIVE_FOR * 2) - }); + let recent = |at: Option<Instant>| { + at.is_some_and(|at| now.saturating_duration_since(at) < ACTIVE_FOR * 2) + }; + self.paths + .retain(|_, path| recent(path.used) || recent(path.last_probe)); let paths = &self.paths; self.chosen .retain(|key, _| key.split(' ').all(|address| paths.contains_key(address))); @@ -965,8 +974,24 @@ mod tests { .is_some() ); assert_eq!(table.counts.probes_sent, 2); - // A peer with one path isn't measured. + // A peer's only path is measured by its keepalives, and not pinged otherwise. table.pinged("libp2p:z", 8, now); + assert!( + table + .pong("libp2p:z", 8, now + Duration::from_millis(30)) + .is_some() + ); + let rtt = table.info("libp2p:z", now).rtt_ms.unwrap(); + assert!((rtt - 30.0).abs() < 1e-6, "{rtt}"); + let later = now + PROBE_EVERY; + assert!( + table + .due(later, &mut next) + .iter() + .all(|(address, _)| address != "libp2p:z") + ); + // Forgotten once the keepalives stop. + table.due(now + ACTIVE_FOR * 2, &mut next); assert!(!table.paths.contains_key("libp2p:z")); } |
