aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-03 02:25:29 +0100
committertv <tv@krebsco.de>2026-11-03 02:25:29 +0100
commite4709cc6ab3fca7f7ce40793aa5cceb64f0bfea3 (patch)
tree72a08d006f5e6c48f1bb4b234a9fbd82e9cbfa23
parentadecfcff1a818cb6d80fd8e24412830ef0988e65 (diff)
retry new peers before probing
-rw-r--r--docs/peers.md4
-rw-r--r--src/state/peer.rs42
-rw-r--r--src/swarm/peer/handshake.rs2
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<Duration> = (0..4).map(|_| peer.next_hello_wait()).collect();
+ // Known for a while: probed.
+ let later = start + FIRST_CONTACT;
+ let probes: Vec<Duration> = (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<Duration> = (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);