diff options
| -rw-r--r-- | docs/peers.md | 4 | ||||
| -rw-r--r-- | src/state/peer.rs | 11 | ||||
| -rw-r--r-- | src/swarm/peer/handshake.rs | 11 | ||||
| -rw-r--r-- | src/swarm/peer/worker.rs | 2 | ||||
| -rw-r--r-- | src/swarm/topology/mod.rs | 10 |
5 files changed, 28 insertions, 10 deletions
diff --git a/docs/peers.md b/docs/peers.md index 3b107bf..4f10f6f 100644 --- a/docs/peers.md +++ b/docs/peers.md @@ -118,7 +118,9 @@ Retries: an unanswered HELLO is resent after 1 s, doubling up to 30 s. To an unreachable peer (dormant, or three unanswered HELLOs) it is a probe: after `dormant_probe`, doubling up to 6 × `dormant_probe` (or `--dormant-probe-max`, which is every wait if shorter); members get probes even when they -aren't neighbours. A member a HELLO_REPLY lists among the sender's active neighbours is +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 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 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* |
