diff options
Diffstat (limited to 'src/state/peer.rs')
| -rw-r--r-- | src/state/peer.rs | 52 |
1 files changed, 49 insertions, 3 deletions
diff --git a/src/state/peer.rs b/src/state/peer.rs index 0d666e6..ce7a3c3 100644 --- a/src/state/peer.rs +++ b/src/state/peer.rs @@ -121,8 +121,11 @@ pub(crate) struct PeerState { pub(crate) last_resync_at: Option<Instant>, /// Since when keepalives show the peer's heads differ from ours with no sync message - /// from it in between (None while they match). + /// from it in between, nor both documents moving on (None while they match). pub(crate) diverged_since: Option<Instant>, + /// Our heads and the peer's at the latest STATUS that showed them differ + /// (`needs_heal`). + diverged_heads: Option<(transport::HeadsSummary, transport::HeadsSummary)>, /// Epoch the peer advertised in its most recent message (late ones aside: see /// `epoch_seq`). @@ -271,6 +274,7 @@ impl PeerState { last_sync_rx: None, last_resync_at: None, diverged_since: None, + diverged_heads: None, epoch: None, epoch_seq: None, run_id: None, @@ -608,6 +612,7 @@ impl PeerState { ) -> bool { if local == remote { self.diverged_since = None; + self.diverged_heads = None; return false; } @@ -615,9 +620,18 @@ impl PeerState { // when we noticed, not from the last sync message: in a quiet swarm that one is // long past, and the first keepalive after a lost message would heal at once, // before the sender's reply timeout could resend it. Any sync message restarts - // the clock. + // the clock, and so do both documents moving on since the last STATUS: while + // writes go on, heads differ by what is still on its way, between neighbours that + // exchange no sync messages as much as between others. A pair whose sync is stuck + // shows the same heads on one side at least (that side gets nothing), or once + // the writes stop, on both. + let moved = self + .diverged_heads + .as_ref() + .is_some_and(|(ours, theirs)| ours != local && theirs != remote); + self.diverged_heads = Some((local.clone(), remote.clone())); let noticed = match self.diverged_since { - Some(since) if self.last_sync_rx.is_none_or(|rx| rx <= since) => since, + Some(since) if !moved && self.last_sync_rx.is_none_or(|rx| rx <= since) => since, _ => *self.diverged_since.insert(now), }; @@ -639,6 +653,7 @@ impl PeerState { return false; } self.diverged_since = None; + self.diverged_heads = None; self.incidents.heals += 1; self.last_resync_at = Some(now); @@ -818,6 +833,37 @@ mod tests { assert!(peer.answer_overdue(overdue + timing.unreached_after / 2)); } + /// Heads that differ while both documents move on (writes on their way) don't heal; + /// heads that differ and stay put on one side do, after `resync_after`. + #[test] + fn heals_only_where_heads_stay_put() { + use crate::document::ChangeHash; + use crate::transport::HeadsSummary; + let timing = Timing::from_keepalive(Duration::from_secs(60), Duration::from_secs(300)); + let mut peer = PeerState::new(true, &timing); + peer.hello_state = HelloState::Established; + let heads = |n: u8| HeadsSummary::of(vec![ChangeHash([n; 32])]); + let start = peer.added_at; + // Both moving on, STATUS after STATUS, for longer than `resync_after`. + for i in 0..10u8 { + let at = start + timing.resync_after * u32::from(i) / 4; + assert!( + !peer.needs_heal(&heads(2 * i + 1), &heads(2 * i + 2), &timing, at), + "both moving on" + ); + } + // Then theirs stay put while ours move on: it gets nothing. + let from = start + timing.resync_after * 3; + assert!(!peer.needs_heal(&heads(100), &heads(50), &timing, from)); + assert!(!peer.needs_heal( + &heads(101), + &heads(50), + &timing, + from + timing.resync_after / 2 + )); + assert!(peer.needs_heal(&heads(102), &heads(50), &timing, from + timing.resync_after)); + } + /// Keepalives are PINGs, every `status_refresh` a STATUS; only when quiet. #[test] fn keepalives_are_pings_and_now_and_then_a_status() { |
