From 4bbadf6fdc4733b4c3c5eeb575508b2f9334ebdb Mon Sep 17 00:00:00 2001 From: tv Date: Mon, 26 Oct 2026 19:29:59 +0100 Subject: keepalive sends head samples alongside digest When there are more than 32 heads that can be send in a single keepalive message, the digest is accompanied by 32 heads, a different window each time, so after a few keepalives every head has been listed. --- src/swarm/relay.rs | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) (limited to 'src/swarm/relay.rs') diff --git a/src/swarm/relay.rs b/src/swarm/relay.rs index 7c56c90..087e028 100644 --- a/src/swarm/relay.rs +++ b/src/swarm/relay.rs @@ -128,20 +128,26 @@ pub(crate) async fn pull_if_behind( if ours == remote.heads { return false; } - let missing = match &remote.heads { - HeadsSummary::Heads(heads) => { - // AutoCommit's lookups take &mut self. - let mut replica = state.doc.write().await; - let missing = heads - .iter() - .any(|head| replica.doc.get_change_by_hash(head).is_none()); - if !missing { - return false; - } - Some(heads.clone()) - } - // Can't tell which heads are missing; the digest differs from ours, though. - HeadsSummary::Digest(_) => None, + // Those of the listed heads we lack. With a digest only a sample is listed; if we + // have all of it, a later keepalive lists others. + let missing: Vec<_> = { + // AutoCommit's lookups take &mut self. + let mut replica = state.doc.write().await; + remote + .heads + .listed() + .iter() + .filter(|head| replica.doc.get_change_by_hash(head).is_none()) + .copied() + .collect() + }; + if missing.is_empty() { + return false; + } + // All heads, when we know them; otherwise those we lack. + let theirs = match remote.heads.heads() { + Some(heads) => heads.to_vec(), + None => missing, }; let Some(peer_state) = state.get_peer_state(peer).await else { @@ -166,13 +172,8 @@ pub(crate) async fn pull_if_behind( pulling.waiting.remove(peer); } - match missing { - // Automerge asks for whatever these heads need that we don't have. - Some(heads) => peer_state.lock().await.sync.pull_from(heads), - // A fresh session's opening carries our heads and bloom filter; the answer - // brings what we lack. - None => peer_state.lock().await.reset_sync(), - } + // Automerge asks for whatever these heads need that we don't have. + peer_state.lock().await.sync.pull_from(theirs); debug!(peer = %peer, "Neighbour has changes we lack; pulling"); let _ = state .sync_events -- cgit v1.3.1