aboutsummaryrefslogtreecommitdiffstats
path: root/src/state/peer.rs
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-03 02:25:29 +0100
committertv <tv@krebsco.de>2026-11-03 02:25:29 +0100
commite4709cc6ab3fca7f7ce40793aa5cceb64f0bfea3 (patch)
tree72a08d006f5e6c48f1bb4b234a9fbd82e9cbfa23 /src/state/peer.rs
parentadecfcff1a818cb6d80fd8e24412830ef0988e65 (diff)
retry new peers before probing
Diffstat (limited to 'src/state/peer.rs')
-rw-r--r--src/state/peer.rs42
1 files changed, 37 insertions, 5 deletions
diff --git a/src/state/peer.rs b/src/state/peer.rs
index ba8379a..24f9188 100644
--- a/src/state/peer.rs
+++ b/src/state/peer.rs
@@ -76,6 +76,11 @@ pub(crate) enum HelloState {
/// comes.
const HELLO_RETRY: Duration = Duration::from_secs(1);
const HELLO_RETRY_MAX: Duration = Duration::from_secs(30);
+/// How long after it became known (`added_at`) a peer never heard from is HELLOed that
+/// often, unreachable or not, rather than probed (`dormant_probe`, ten keepalives):
+/// members that restart together may fail to reach each other at first, as the others
+/// aren't up yet, and would otherwise wait for a probe to find each other.
+const FIRST_CONTACT: Duration = Duration::from_secs(300);
#[derive(Clone)]
pub(crate) struct PeerState {
@@ -341,9 +346,12 @@ impl PeerState {
)
}
- /// The wait before the next HELLO: a probe's if unreachable.
- pub(crate) fn next_hello_wait(&mut self) -> Duration {
- if self.is_unreachable() {
+ /// The wait before the next HELLO: a probe's if unreachable, but while a peer never
+ /// heard from is new (`FIRST_CONTACT`).
+ pub(crate) fn next_hello_wait(&mut self, now: Instant) -> Duration {
+ let new = self.last_seen.is_none()
+ && now.saturating_duration_since(self.added_at) < FIRST_CONTACT;
+ if self.is_unreachable() && !new {
self.probe_backoff.next()
} else {
self.hello_backoff.next()
@@ -691,7 +699,9 @@ mod tests {
}
assert!(peer.is_unreachable());
- let probes: Vec<Duration> = (0..4).map(|_| peer.next_hello_wait()).collect();
+ // Known for a while: probed.
+ let later = start + FIRST_CONTACT;
+ let probes: Vec<Duration> = (0..4).map(|_| peer.next_hello_wait(later)).collect();
let probe = timing.dormant_probe;
assert_eq!(probes, [probe, probe * 2, probe * 4, probe * 6]);
peer.hello_sent(start + probe * 20);
@@ -702,6 +712,28 @@ mod tests {
peer.hello_sent(start + probe * 22);
peer.hello_sent(start + probe * 22);
peer.hello_sent(start + probe * 22);
- assert_eq!(peer.next_hello_wait(), probe);
+ assert_eq!(peer.next_hello_wait(start + probe * 22), probe);
+ }
+
+ /// A peer never heard from is HELLOed at the HELLO backoff while it is new
+ /// (`FIRST_CONTACT`), unreachable or not; probed after.
+ #[test]
+ fn new_peers_retried_before_probed() {
+ 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;
+ for _ in 0..UNREACHABLE_AFTER_HELLOS {
+ peer.hello_sent(start);
+ }
+ assert!(peer.is_unreachable());
+ let waits: Vec<Duration> = (0..7).map(|_| peer.next_hello_wait(start)).collect();
+ assert!(
+ waits.iter().all(|wait| *wait <= HELLO_RETRY_MAX),
+ "{waits:?}"
+ );
+ assert_eq!(
+ peer.next_hello_wait(start + FIRST_CONTACT),
+ timing.dormant_probe
+ );
}
}