summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-25 23:50:29 +0100
committertv <tv@krebsco.de>2026-10-25 23:50:29 +0100
commit0039ffe04d290935936d33e681c2a0f2575c4264 (patch)
tree1bfcaa189bcc5670b1959d766dfdc0e9f5816334 /src
parenta232ec43702cdd23614737351120fd3b065e987d (diff)
refresh peers after stall
Diffstat (limited to 'src')
-rw-r--r--src/simtest/timing.rs38
-rw-r--r--src/swarm/peer.rs13
-rw-r--r--src/swarm/source.rs7
3 files changed, 57 insertions, 1 deletions
diff --git a/src/simtest/timing.rs b/src/simtest/timing.rs
index 25ecf89..e4981dd 100644
--- a/src/simtest/timing.rs
+++ b/src/simtest/timing.rs
@@ -146,6 +146,44 @@ async fn stalled_catch_up_source_is_replaced() {
assert!(sources >= 2, "{sources} catch-up sources");
}
+/// A joining node whose only known peer stalls as its catch-up source asks it again
+/// (a fresh HELLO): the reply lists the peers that joined meanwhile, and one of them
+/// takes over.
+#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
+async fn stalled_source_refreshes_the_peer_list() {
+ let sim = Sim::new(7);
+ let a = sim.start("a", &[], Start::Genesis);
+ let a = &a;
+ sim.wait_ready(&[a], SOON).await;
+ write(a, "data", json!("x")).await;
+
+ // c knows only a, which lists nobody: there is nobody else yet.
+ sim.net.set_link(
+ "a",
+ "c",
+ LinkParams {
+ drop: vec![MessageKind::Sync],
+ ..LinkParams::default()
+ },
+ );
+ let c = sim.start("c", &["a"], Start::Join);
+ let c = &c;
+ sim.wait_until("c knows a", SOON, move || async move {
+ c.get_peer_state("a").await.is_some()
+ })
+ .await;
+
+ // b joins meanwhile; c hears of it only by asking a again (after giving it up, or
+ // earlier if the keepalive heal re-handshakes first).
+ let b = sim.start("b", &["a"], Start::Join);
+ let b = &b;
+ sim.wait_ready(&[b], SOON).await;
+
+ sim.wait_ready(&[c], LOSSY).await;
+ assert_eq!(read(c, "data").await, Some(json!("x")));
+ assert!(c.get_peer_state("b").await.is_some());
+}
+
/// A peer that hears nothing for `dormant_after` is dormant; once messages flow again
/// it is active, re-handshakes, and syncs what it missed.
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
diff --git a/src/swarm/peer.rs b/src/swarm/peer.rs
index e8248bf..1240966 100644
--- a/src/swarm/peer.rs
+++ b/src/swarm/peer.rs
@@ -221,6 +221,19 @@ pub(crate) async fn remember_peer(state: &AppState, peer: &str) {
}
}
+/// Starts a new handshake with `peer` even though one is complete: its reply resets
+/// sync on both sides and brings its current peer list (`swarm::source`, after giving
+/// it up as the catch-up source).
+pub(crate) async fn rehandshake(state: &AppState, peer: &str) {
+ if let Some(peer_state) = state.get_peer_state(peer).await {
+ let mut peer_state = peer_state.lock().await;
+ if peer_state.hello_state == HelloState::Established {
+ peer_state.hello_state = HelloState::NotSent;
+ }
+ }
+ ensure_handshake(state, peer).await;
+}
+
pub(crate) async fn send_hello_once(state: &AppState, peer: &str) -> Result<()> {
let Some(peer_state_mutex) = state.get_peer_state(peer).await else {
trace!(peer = %peer, "Peer was forgotten; not sending HELLO");
diff --git a/src/swarm/source.rs b/src/swarm/source.rs
index 9eac445..b1701b3 100644
--- a/src/swarm/source.rs
+++ b/src/swarm/source.rs
@@ -23,7 +23,10 @@
//! `Timing::delivery_limit`, after which Mycelium has delivered or dropped anything it
//! sent before. It isn't chosen again for another `delivery_limit`, so another peer
//! takes over rather than the same one stalling again; if it is the only candidate,
-//! catching up waits that long before trying it again. Meanwhile, every minute without a sync message from it is logged as a
+//! catching up waits that long before trying it again. It also gets a fresh HELLO: a
+//! joining node learns of other peers only from HELLO_REPLYs, and the one it got from
+//! this peer may have listed none (a peer lists only its neighbours, which a new member
+//! isn't until its registration has spread). Meanwhile, every minute without a sync message from it is logged as a
//! warning.
use std::sync::atomic::Ordering;
@@ -203,6 +206,8 @@ pub(crate) fn reply_timed_out(state: &AppState, peer: &str) {
"Catch-up source doesn't answer our sync messages; catching up from another peer"
);
resync_everyone(state);
+ let (state, peer) = (state.clone(), peer.to_owned());
+ tokio::spawn(async move { crate::swarm::peer::rehandshake(&state, &peer).await });
}
}