aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-10-31 06:19:34 +0100
committertv <tv@krebsco.de>2026-10-31 06:19:34 +0100
commit4a587f665b2eddb8cda4dce517ecf8a8e22c6cad (patch)
tree6cec9278a333798038c1dd751d4df5bed9043479 /src
parentc68cc63f6f3e8b025ce114ef26162b62ca67782e (diff)
dashboard: try to get links dormant immediately
Diffstat (limited to 'src')
-rw-r--r--src/state/peer.rs39
-rw-r--r--src/swarm/links.rs15
-rw-r--r--src/swarm/peer/inbound.rs18
3 files changed, 59 insertions, 13 deletions
diff --git a/src/state/peer.rs b/src/state/peer.rs
index 99f912b..f6e8786 100644
--- a/src/state/peer.rs
+++ b/src/state/peer.rs
@@ -137,6 +137,10 @@ pub(crate) struct PeerState {
/// How many heads its most recent keepalive listed (None if it sent a digest).
pub(crate) reported_heads: Option<usize>,
+ /// It said farewell (`say_farewell`): what else that run of it still sends while it
+ /// stops doesn't make it active again; a message from another run does (`returned`).
+ pub(crate) left: bool,
+
/// 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
@@ -212,6 +216,7 @@ impl PeerState {
reports_ready: None,
syncs_with_us: None,
reported_heads: None,
+ left: false,
hello_attempts: 0,
unreachable_since: None,
incidents: Incidents::default(),
@@ -244,9 +249,13 @@ impl PeerState {
now.saturating_duration_since(self.last_seen.unwrap_or(self.added_at))
}
- /// Records inbound activity. Returns true if the peer was dormant.
+ /// Records inbound activity. Returns true if the peer was dormant. From a peer that
+ /// said farewell (`left`), only that it was heard from.
pub(crate) fn mark_seen(&mut self, now: Instant) -> bool {
self.last_seen = Some(now);
+ if self.left {
+ return false;
+ }
self.hello_attempts = 0;
self.unreachable_since = None;
self.probe_backoff.reset();
@@ -333,6 +342,7 @@ impl PeerState {
/// The peer said farewell (it is shutting down): Dormant now, without waiting for
/// its silence, and not counted as an incident. Returns true on the transition.
pub(crate) fn say_farewell(&mut self, now: Instant) -> bool {
+ self.left = true;
if self.liveness.is_dormant() {
return false;
}
@@ -340,6 +350,14 @@ impl PeerState {
true
}
+ /// A message from `run` of the peer (a HELLO's or HELLO_REPLY's): a run other than
+ /// the one that said farewell is back (`left`).
+ pub(crate) fn returned(&mut self, run: crate::transport::RunId) {
+ if self.run_id != Some(run) {
+ self.left = false;
+ }
+ }
+
fn go_dormant(&mut self, now: Instant) {
self.liveness = Liveness::Dormant { since: now };
self.unreachable_since.get_or_insert(now);
@@ -488,6 +506,25 @@ mod tests {
assert!(silent.liveness.is_dormant());
}
+ /// After its farewell, a peer stays dormant whatever its stopping run still sends;
+ /// its next run makes it active again.
+ #[test]
+ fn dormant_after_farewell_until_back() {
+ let timing = Timing::from_keepalive(Duration::from_secs(60), Duration::from_secs(300));
+ let mut peer = PeerState::new(true, &timing);
+ let now = peer.added_at;
+ peer.run_id = Some([1; 16]);
+ peer.mark_seen(now);
+ assert!(peer.say_farewell(now));
+ assert!(!peer.mark_seen(now), "a straggler from the same run");
+ assert!(peer.liveness.is_dormant() && peer.is_unreachable());
+ peer.returned([1; 16]);
+ assert!(!peer.mark_seen(now), "the same run again");
+ peer.returned([2; 16]);
+ assert!(peer.mark_seen(now), "its next run");
+ assert!(!peer.liveness.is_dormant() && !peer.is_unreachable());
+ }
+
/// The two sides of a pair take turns: each sends every other interval.
#[test]
fn keepalives_take_turns() {
diff --git a/src/swarm/links.rs b/src/swarm/links.rs
index 18b2bdd..82cd991 100644
--- a/src/swarm/links.rs
+++ b/src/swarm/links.rs
@@ -1,8 +1,8 @@
//! How members are linked, for a map of the swarm (the dashboard, `api::dashboard`): a
//! node asks a member with LINKS, and the member answers with a LINKS_REPLY listing its
-//! neighbours, each with the transport its messages go over, whether only through a
-//! relay, the round trip and whether the neighbour is dormant: as the member measures
-//! them, which no other node can.
+//! neighbours and the peers it holds dormant (`own_links`), each with the transport its
+//! messages go over, whether only through a relay, the round trip and whether it is
+//! dormant: as the member measures them, which no other node can.
//!
//! Every node answers, whatever its build: one with a dashboard asks any member, which
//! may have none. Only members (in the member list) are answered, as the links name
@@ -101,14 +101,17 @@ impl Links {
}
}
-/// This node's links: its neighbours, as it measures them.
+/// This node's links: its neighbours, and the peers it holds dormant, as it measures
+/// them. A neighbour that goes dormant is one no more (it is unreachable now), but its
+/// link stays listed, dormant, until it is heard from again: left out, the link would
+/// show only as that peer last reported it, from before.
pub(crate) async fn own_links(state: &AppState) -> Vec<Link> {
let mut links = Vec::new();
for (peer, peer_state) in state.peers_snapshot().await {
- if !topology::is_neighbour(state, &peer) {
+ let dormant = peer_state.lock().await.liveness.is_dormant();
+ if !dormant && !topology::is_neighbour(state, &peer) {
continue;
}
- let dormant = peer_state.lock().await.liveness.is_dormant();
let destination = state.transport.destination(&peer);
let transport = destination
.as_ref()
diff --git a/src/swarm/peer/inbound.rs b/src/swarm/peer/inbound.rs
index b4a96e4..b57feaf 100644
--- a/src/swarm/peer/inbound.rs
+++ b/src/swarm/peer/inbound.rs
@@ -58,17 +58,23 @@ pub(crate) async fn handle_message(
remember_peer(state, &peer).await;
- // Any inbound message proves liveness. When we heard from it before tells a
- // restarted peer how long it was away (in our HELLO_REPLY).
- let peer_state = state.get_or_create_peer_state(&peer).await;
- let (reactivated, previously_seen) =
- heard_from(state, &peer, &peer_state, Instant::now(), |_| {}).await;
-
let run = match &message {
transport::ParsedMessage::Hello(hello) => Some(hello.run_id),
transport::ParsedMessage::HelloReply(reply) => Some(reply.sender_run_id),
_ => None,
};
+
+ // Any inbound message proves liveness (but from a run that said farewell: its next
+ // one does). When we heard from it before tells a restarted peer how long it was
+ // away (in our HELLO_REPLY).
+ let peer_state = state.get_or_create_peer_state(&peer).await;
+ let (reactivated, previously_seen) =
+ heard_from(state, &peer, &peer_state, Instant::now(), |peer_state| {
+ if let Some(run) = run {
+ peer_state.returned(run);
+ }
+ })
+ .await;
let same_epoch = observe_peer_epoch(state, &peer, epoch, epoch_seq, run).await;
match message {