aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/peers.md5
-rw-r--r--src/state/peer.rs52
-rw-r--r--src/swarm/peer/inbound.rs15
-rw-r--r--src/swarm/peer/keepalive.rs6
4 files changed, 75 insertions, 3 deletions
diff --git a/docs/peers.md b/docs/peers.md
index be04b16..cacceb5 100644
--- a/docs/peers.md
+++ b/docs/peers.md
@@ -114,7 +114,10 @@ With a swarm key (`--swarm-key-file`), every HELLO and HELLO_REPLY carries
until then everything else it sends is ignored, and a handshake without a valid proof
gets no answer at all.
-Retries: an unanswered HELLO is resent after 1 s, doubling up to 30 s. To an
+Retries: an unanswered HELLO is resent after 1 s, doubling up to 30 s, but at once
+(at most every second) when anything else comes from the peer meanwhile: it is up and
+reaches us, so the HELLO or its reply was lost, and waiting out the backoff (or a
+probe's) would only delay the handshake. 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
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() {
diff --git a/src/swarm/peer/inbound.rs b/src/swarm/peer/inbound.rs
index 28066cb..cfafbd3 100644
--- a/src/swarm/peer/inbound.rs
+++ b/src/swarm/peer/inbound.rs
@@ -68,13 +68,19 @@ pub(crate) async fn handle_message(
// 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 mut hello_now = false;
let (reactivated, previously_seen) =
heard_from(state, &peer, &peer_state, Instant::now(), |peer_state| {
- if let Some(run) = run {
- peer_state.returned(run);
+ match run {
+ Some(run) => peer_state.returned(run),
+ // Up and reaching us, while we wait for its HELLO_REPLY.
+ None => hello_now = peer_state.hello_again_now(Instant::now()),
}
})
.await;
+ if hello_now {
+ state.peers_due.now();
+ }
let same_epoch = observe_peer_epoch(state, &peer, epoch, epoch_seq, run).await;
match message {
@@ -83,7 +89,12 @@ pub(crate) async fn handle_message(
// restart. It becomes a member right away; our reply makes us one for it.
let Some(swarm_id) = state.swarm_id().await else {
// We are joining ourselves; the peer retries until we know our swarm.
+ // It is up and reaches us: if we wait for its HELLO_REPLY, our HELLO or
+ // the reply was lost, and ours goes again now.
debug!(peer = %peer, "HELLO before we know our swarm; not answering yet");
+ if peer_state.lock().await.hello_again_now(Instant::now()) {
+ state.peers_due.now();
+ }
return Ok(());
};
if let Some(theirs) = &hello.swarm_id
diff --git a/src/swarm/peer/keepalive.rs b/src/swarm/peer/keepalive.rs
index 0f34eb8..aa34e70 100644
--- a/src/swarm/peer/keepalive.rs
+++ b/src/swarm/peer/keepalive.rs
@@ -84,7 +84,10 @@ pub(super) async fn note_alive(state: &AppState, peer: &str, message: &ParsedMes
return;
};
let ping = matches!(message, ParsedMessage::Ping(_));
+ let mut hello_now = false;
let (reactivated, _) = heard_from(state, peer, &peer_state, now, |peer_state| {
+ // Up and reaching us, while we wait for its HELLO_REPLY.
+ hello_now = peer_state.hello_again_now(now);
if ping {
// Its keepalive (or a path probe): the next is ours.
peer_state.keepalive_received_at = Some(now);
@@ -94,6 +97,9 @@ pub(super) async fn note_alive(state: &AppState, peer: &str, message: &ParsedMes
}
})
.await;
+ if hello_now {
+ state.peers_due.now();
+ }
if reactivated {
ensure_handshake(state, peer).await;
}