aboutsummaryrefslogtreecommitdiffstats
path: root/src/state
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-04 19:45:14 +0100
committertv <tv@krebsco.de>2026-11-04 19:45:14 +0100
commit07ef1e4e562d977e72f0a6b702736ae3645b45c7 (patch)
treec48b0917f602d63f95ef195c899069a16380eefb /src/state
parentd2c1fb0b87a4c74a7f18ec9ad0a62e2c195523a0 (diff)
hello again when heard from
While we wait for a peer's HELLO_REPLY, anything else that comes from it makes our HELLO go again at once, at most once a second, instead of when the backoff says.
Diffstat (limited to 'src/state')
-rw-r--r--src/state/peer.rs52
1 files changed, 52 insertions, 0 deletions
diff --git a/src/state/peer.rs b/src/state/peer.rs
index ce7a3c3..32b35fc 100644
--- a/src/state/peer.rs
+++ b/src/state/peer.rs
@@ -165,6 +165,8 @@ pub(crate) struct PeerState {
/// HELLOs sent in the current handshake attempt without hearing anything back.
pub(crate) hello_attempts: u32,
+ /// When our last HELLO to it went out (`hello_again_now`).
+ last_hello_at: Option<Instant>,
/// 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 (by its HELLO_REPLY, or by the member list) gets
@@ -288,6 +290,7 @@ impl PeerState {
tree_writer: None,
left: false,
hello_attempts: 0,
+ last_hello_at: None,
unreachable_since: None,
incidents: Incidents::default(),
whole_failed: None,
@@ -377,12 +380,34 @@ impl PeerState {
/// 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.last_hello_at = Some(now);
self.hello_attempts += 1;
if self.hello_attempts >= UNREACHABLE_AFTER_HELLOS {
self.unreachable_since.get_or_insert(now);
}
}
+ /// A message from it came while we wait for its HELLO_REPLY: it is up, and reaches
+ /// us, now. So our HELLO or its reply was lost: the HELLO goes again now (the peer
+ /// worker sends it), rather than when the backoff says, which after a few lost ones
+ /// is up to `HELLO_RETRY_MAX` (or a probe's wait) away; at most every `HELLO_RETRY`.
+ /// Not for a HELLO or HELLO_REPLY of its own, which the handshake handles (but for a
+ /// HELLO while we are joining, which we don't answer yet). Returns whether the HELLO
+ /// is due now.
+ pub(crate) fn hello_again_now(&mut self, now: Instant) -> bool {
+ let HelloState::AwaitingReply { next_attempt } = self.hello_state else {
+ return false;
+ };
+ let lately = self
+ .last_hello_at
+ .is_some_and(|at| now.saturating_duration_since(at) < HELLO_RETRY);
+ if next_attempt <= now || lately {
+ return false;
+ }
+ self.hello_state = HelloState::AwaitingReply { next_attempt: now };
+ true
+ }
+
/// Whether we sync with it on `epoch`: an active member on that epoch (what our
/// STATUS tells it as `syncs_with_you`).
pub(crate) fn syncs_on(&self, epoch: Epoch) -> bool {
@@ -864,6 +889,33 @@ mod tests {
assert!(peer.needs_heal(&heads(102), &heads(50), &timing, from + timing.resync_after));
}
+ /// A message from a peer whose HELLO_REPLY we await makes the HELLO due now, but not
+ /// within `HELLO_RETRY` of the last one, and not where none is awaited.
+ #[test]
+ fn hello_again_when_heard_from() {
+ let timing = Timing::from_keepalive(Duration::from_secs(60), Duration::from_secs(300));
+ let mut peer = PeerState::new(true, &timing);
+ let start = peer.added_at;
+ assert!(!peer.hello_again_now(start), "no HELLO sent");
+ peer.hello_sent(start);
+ let later = start + Duration::from_secs(30);
+ peer.hello_state = HelloState::AwaitingReply {
+ next_attempt: later,
+ };
+ assert!(!peer.hello_again_now(start + HELLO_RETRY / 2), "just sent");
+ let heard = start + HELLO_RETRY;
+ assert!(peer.hello_again_now(heard));
+ assert_eq!(
+ peer.hello_state,
+ HelloState::AwaitingReply {
+ next_attempt: heard
+ }
+ );
+ assert!(!peer.hello_again_now(heard), "due already");
+ peer.hello_state = HelloState::Established;
+ assert!(!peer.hello_again_now(later));
+ }
+
/// Keepalives are PINGs, every `status_refresh` a STATUS; only when quiet.
#[test]
fn keepalives_are_pings_and_now_and_then_a_status() {