aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-03 02:00:58 +0100
committertv <tv@krebsco.de>2026-11-03 02:00:58 +0100
commitadecfcff1a818cb6d80fd8e24412830ef0988e65 (patch)
treeeca30977e4308db344eeb4231efa8959e3199355 /src
parent02989b6a22a7bbd2151d6a4ea4fe6dddc9f2995b (diff)
probe list members
Diffstat (limited to 'src')
-rw-r--r--src/state/peer.rs11
-rw-r--r--src/swarm/peer/handshake.rs11
-rw-r--r--src/swarm/peer/worker.rs2
-rw-r--r--src/swarm/topology/mod.rs10
4 files changed, 25 insertions, 9 deletions
diff --git a/src/state/peer.rs b/src/state/peer.rs
index 063b72b..ba8379a 100644
--- a/src/state/peer.rs
+++ b/src/state/peer.rs
@@ -146,7 +146,8 @@ pub(crate) struct PeerState {
pub(crate) hello_attempts: u32,
/// Since when we consider the peer unreachable (three unanswered HELLOs, or
/// dormant). Only a message from it clears it (`mark_seen`): until then it is no
- /// neighbour candidate, and a member gets probes (`probe_backoff`) instead.
+ /// neighbour candidate, and a member (by its HELLO_REPLY, or by the member list) gets
+ /// probes (`probe_backoff`) instead.
pub(crate) unreachable_since: Option<Instant>,
/// What went wrong with this peer so far (counted only, for /peers and /admin/stats).
@@ -352,8 +353,12 @@ impl PeerState {
/// No more HELLO retries (the peer isn't our neighbour any more). A HELLO in flight
/// finishes; a later handshake starts over. An unreachable member keeps its probes:
/// it is no neighbour because it is unreachable, and only its answer can change that.
- pub(crate) fn stop_hello_retries(&mut self) {
- let probed = self.is_unreachable() && matches!(self.standing, Standing::Member { .. });
+ /// A member is one whose HELLO_REPLY said so, or one `listed` in the member list: after
+ /// a restart, none has answered yet, and members that restart together may each fail
+ /// to reach the others at first; without probes, none would try again.
+ pub(crate) fn stop_hello_retries(&mut self, listed: bool) {
+ let probed =
+ self.is_unreachable() && (listed || matches!(self.standing, Standing::Member { .. }));
if !probed && matches!(self.hello_state, HelloState::AwaitingReply { .. }) {
self.hello_state = HelloState::NotSent;
}
diff --git a/src/swarm/peer/handshake.rs b/src/swarm/peer/handshake.rs
index 072545b..1ca955b 100644
--- a/src/swarm/peer/handshake.rs
+++ b/src/swarm/peer/handshake.rs
@@ -330,15 +330,16 @@ pub(crate) async fn probe_when_reachable(state: AppState, reachable: Arc<tokio::
/// Brings `peer`'s next probe forward to now, if it is a member we can't reach (dormant,
/// or unanswered HELLOs) whose probe is pending: the peer worker sends it (woken for
-/// it). Whether it was.
+/// it). A member by its HELLO_REPLY, or by the member list (`stop_hello_retries`).
+/// Whether it was.
async fn probe_now(state: &AppState, peer: &str) -> bool {
let Some(peer_state) = state.get_peer_state(peer).await else {
return false;
};
let mut peer_state = peer_state.lock().await;
- if !matches!(peer_state.standing, Standing::Member { .. })
- || !(peer_state.liveness.is_dormant() || peer_state.is_unreachable())
- {
+ let member =
+ matches!(peer_state.standing, Standing::Member { .. }) || topology::is_listed(state, peer);
+ if !member || !(peer_state.liveness.is_dormant() || peer_state.is_unreachable()) {
return false;
}
match &mut peer_state.hello_state {
@@ -372,7 +373,7 @@ pub(crate) async fn apply_neighbour_change(state: &AppState, change: topology::N
for peer in &change.removed {
if let Some(peer_state) = state.get_peer_state(peer).await {
let mut peer_state = peer_state.lock().await;
- peer_state.stop_hello_retries();
+ peer_state.stop_hello_retries(topology::is_listed(state, peer));
peer_state.reset_sync();
}
}
diff --git a/src/swarm/peer/worker.rs b/src/swarm/peer/worker.rs
index 6196571..a803922 100644
--- a/src/swarm/peer/worker.rs
+++ b/src/swarm/peer/worker.rs
@@ -99,7 +99,7 @@ pub(crate) async fn run_peer_worker(state: AppState) {
None
};
if !neighbour {
- peer_state.stop_hello_retries();
+ peer_state.stop_hello_retries(topology::is_listed(&state, peer));
}
let unanswered = peer_state.take_overdue_sync_reply(now);
(became_dormant, keepalive, unanswered)
diff --git a/src/swarm/topology/mod.rs b/src/swarm/topology/mod.rs
index 992c448..e5dacdd 100644
--- a/src/swarm/topology/mod.rs
+++ b/src/swarm/topology/mod.rs
@@ -113,6 +113,16 @@ pub(crate) fn summary(state: &AppState) -> Option<Summary> {
})
}
+/// Whether `peer` is in the member list the neighbours were computed from (false until
+/// the view is known).
+pub(crate) fn is_listed(state: &AppState, peer: &str) -> bool {
+ state
+ .neighbours
+ .read_or_recover()
+ .as_ref()
+ .is_some_and(|view| view.members.iter().any(|member| member == peer))
+}
+
/// Whether we initiate contact with `peer`: HELLOs, keepalives, dormancy checks, sync
/// openings. Everyone is a neighbour until the view is known (while joining, or before
/// any member registered), so joining works as in a full mesh. We always *answer*