From e4709cc6ab3fca7f7ce40793aa5cceb64f0bfea3 Mon Sep 17 00:00:00 2001 From: tv Date: Tue, 3 Nov 2026 02:25:29 +0100 Subject: retry new peers before probing --- docs/peers.md | 4 +++- src/state/peer.rs | 42 +++++++++++++++++++++++++++++++++++++----- src/swarm/peer/handshake.rs | 2 +- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/docs/peers.md b/docs/peers.md index 4f10f6f..9937c63 100644 --- a/docs/peers.md +++ b/docs/peers.md @@ -120,7 +120,9 @@ unreachable peer (dormant, or three unanswered HELLOs) it is a probe: after every wait if shorter); members get probes even when they aren't neighbours: those in the member list, as well as those whose HELLO_REPLY said so. After a restart none has answered yet, and members that restart together may each fail -to reach the others at first (not up yet): without probes, none would try again. A member a HELLO_REPLY lists among the sender's active neighbours is +to reach the others at first (not up yet): without probes, none would try again. Nor +should they wait for one: a peer never heard from gets HELLOs at the retry backoff (up +to 30 s apart) for its first 5 minutes known, unreachable or not, and probes only after. A member a HELLO_REPLY lists among the sender's active neighbours is probed at once: the sender reaches it, so after a partition the first pair to find each other again brings the others' probes forward, and the sides reconnect within seconds rather than pair by pair. So are all unreachable members once this node becomes diff --git a/src/state/peer.rs b/src/state/peer.rs index ba8379a..24f9188 100644 --- a/src/state/peer.rs +++ b/src/state/peer.rs @@ -76,6 +76,11 @@ pub(crate) enum HelloState { /// comes. const HELLO_RETRY: Duration = Duration::from_secs(1); const HELLO_RETRY_MAX: Duration = Duration::from_secs(30); +/// How long after it became known (`added_at`) a peer never heard from is HELLOed that +/// often, unreachable or not, rather than probed (`dormant_probe`, ten keepalives): +/// members that restart together may fail to reach each other at first, as the others +/// aren't up yet, and would otherwise wait for a probe to find each other. +const FIRST_CONTACT: Duration = Duration::from_secs(300); #[derive(Clone)] pub(crate) struct PeerState { @@ -341,9 +346,12 @@ impl PeerState { ) } - /// The wait before the next HELLO: a probe's if unreachable. - pub(crate) fn next_hello_wait(&mut self) -> Duration { - if self.is_unreachable() { + /// The wait before the next HELLO: a probe's if unreachable, but while a peer never + /// heard from is new (`FIRST_CONTACT`). + pub(crate) fn next_hello_wait(&mut self, now: Instant) -> Duration { + let new = self.last_seen.is_none() + && now.saturating_duration_since(self.added_at) < FIRST_CONTACT; + if self.is_unreachable() && !new { self.probe_backoff.next() } else { self.hello_backoff.next() @@ -691,7 +699,9 @@ mod tests { } assert!(peer.is_unreachable()); - let probes: Vec = (0..4).map(|_| peer.next_hello_wait()).collect(); + // Known for a while: probed. + let later = start + FIRST_CONTACT; + let probes: Vec = (0..4).map(|_| peer.next_hello_wait(later)).collect(); let probe = timing.dormant_probe; assert_eq!(probes, [probe, probe * 2, probe * 4, probe * 6]); peer.hello_sent(start + probe * 20); @@ -702,6 +712,28 @@ mod tests { peer.hello_sent(start + probe * 22); peer.hello_sent(start + probe * 22); peer.hello_sent(start + probe * 22); - assert_eq!(peer.next_hello_wait(), probe); + assert_eq!(peer.next_hello_wait(start + probe * 22), probe); + } + + /// A peer never heard from is HELLOed at the HELLO backoff while it is new + /// (`FIRST_CONTACT`), unreachable or not; probed after. + #[test] + fn new_peers_retried_before_probed() { + let timing = Timing::from_keepalive(Duration::from_secs(60), Duration::from_secs(300)); + let mut peer = PeerState::new(true, &timing); + let start = peer.added_at; + for _ in 0..UNREACHABLE_AFTER_HELLOS { + peer.hello_sent(start); + } + assert!(peer.is_unreachable()); + let waits: Vec = (0..7).map(|_| peer.next_hello_wait(start)).collect(); + assert!( + waits.iter().all(|wait| *wait <= HELLO_RETRY_MAX), + "{waits:?}" + ); + assert_eq!( + peer.next_hello_wait(start + FIRST_CONTACT), + timing.dormant_probe + ); } } diff --git a/src/swarm/peer/handshake.rs b/src/swarm/peer/handshake.rs index 1ca955b..769b010 100644 --- a/src/swarm/peer/handshake.rs +++ b/src/swarm/peer/handshake.rs @@ -148,7 +148,7 @@ async fn transmit_hello(state: &AppState, peer: &str) -> Result<()> { if peer_state.hello_state == HelloState::Sending { topology::change_peer(state, &mut peer_state, |p| p.hello_sent(Instant::now())); - let wait = peer_state.next_hello_wait(); + let wait = peer_state.next_hello_wait(Instant::now()); let next_attempt = Instant::now() + wait; peer_state.hello_state = HelloState::AwaitingReply { next_attempt }; state.peers_due.by(next_attempt); -- cgit v1.3.1