summaryrefslogtreecommitdiffstats
path: root/src/state.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-24 04:20:21 +0200
committertv <tv@krebsco.de>2026-10-24 04:21:11 +0200
commitc2468964b11bd3606f03375c370fd181a1723113 (patch)
tree96c9c4a126efc928716491b4c110a4da7de6ead7 /src/state.rs
parent5710d1a367a1914d09e0eefe6984408c95a7dd2e (diff)
find reachable neighbours
Diffstat (limited to 'src/state.rs')
-rw-r--r--src/state.rs30
1 files changed, 30 insertions, 0 deletions
diff --git a/src/state.rs b/src/state.rs
index 64fe6a4..7574e35 100644
--- a/src/state.rs
+++ b/src/state.rs
@@ -137,8 +137,18 @@ pub(crate) struct PeerState {
/// How many heads its most recent keepalive listed (None if it sent a digest).
pub(crate) reported_heads: Option<usize>,
+
+ /// HELLOs sent in the current handshake attempt without hearing anything back.
+ pub(crate) hello_attempts: u32,
+ /// Since when we consider the peer unreachable (three unanswered HELLOs, or
+ /// dormant). Cleared by any message from it; ignored after `dormant_probe`, so the
+ /// peer becomes a neighbour candidate again and gets tried once more.
+ pub(crate) unreachable_since: Option<Instant>,
}
+/// Unanswered HELLOs after which a peer counts as unreachable (1 + 2 + 4 s apart).
+const UNREACHABLE_AFTER_HELLOS: u32 = 3;
+
/// One Automerge sync with a peer, from a reset to the next. Everything a reset must
/// forget lives here, so a reset replaces it whole and can't miss a field.
#[derive(Clone)]
@@ -284,6 +294,8 @@ impl PeerState {
reports_ready: None,
syncs_with_us: None,
reported_heads: None,
+ hello_attempts: 0,
+ unreachable_since: None,
}
}
@@ -309,6 +321,8 @@ impl PeerState {
/// Records inbound activity. Returns true if the peer was dormant.
pub(crate) fn mark_seen(&mut self, now: Instant) -> bool {
self.last_seen = Some(now);
+ self.hello_attempts = 0;
+ self.unreachable_since = None;
if matches!(self.liveness, Liveness::Dormant { .. }) {
self.liveness = Liveness::Active;
@@ -321,6 +335,21 @@ impl PeerState {
false
}
+ /// A HELLO went out (or failed to). After the third unanswered one, the peer counts
+ /// as unreachable.
+ pub(crate) fn hello_sent(&mut self, now: Instant) {
+ self.hello_attempts += 1;
+ if self.hello_attempts >= UNREACHABLE_AFTER_HELLOS {
+ self.unreachable_since.get_or_insert(now);
+ }
+ }
+
+ /// Whether to leave the peer out when choosing neighbours (`swarm::topology`).
+ pub(crate) fn is_unreachable(&self, timing: &Timing, now: Instant) -> bool {
+ self.unreachable_since
+ .is_some_and(|since| now.saturating_duration_since(since) < timing.dormant_probe)
+ }
+
/// No more HELLO retries (the peer isn't our neighbour any more). A HELLO in flight
/// finishes; a later handshake starts over.
pub(crate) fn stop_hello_retries(&mut self) {
@@ -336,6 +365,7 @@ impl PeerState {
}
self.liveness = Liveness::Dormant { since: now };
+ self.unreachable_since.get_or_insert(now);
self.reset_sync();
if self.hello_state != HelloState::Sending {