diff options
Diffstat (limited to 'src/state')
| -rw-r--r-- | src/state/peer.rs | 52 |
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() { |
