From 065bf2f9e738eae219da1fe0557ec22f94f10b7a Mon Sep 17 00:00:00 2001 From: tv Date: Thu, 5 Nov 2026 16:10:14 +0100 Subject: waiting not twice --- src/document/changes.rs | 7 +++++++ src/simtest/bench.rs | 4 +++- src/swarm/stats.rs | 9 +++++++++ src/swarm/sync.rs | 9 ++++++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/document/changes.rs b/src/document/changes.rs index 1091261..9a07fc0 100644 --- a/src/document/changes.rs +++ b/src/document/changes.rs @@ -8,6 +8,13 @@ use anyhow::{Context, Result, anyhow, bail}; use super::{Change, ChangeHash, Document, ReadDoc}; +/// Whether `doc` has every change of `heads`. +pub(crate) fn holds_all(doc: &Document, heads: &[ChangeHash]) -> bool { + heads + .iter() + .all(|head| doc.get_change_by_hash(head).is_some()) +} + /// Whether the history of `theirs` (of them, those `doc` has) holds all of `ours` /// (heads of `doc`): a peer at `theirs` has every change we have. pub(crate) fn covers(doc: &Document, theirs: &[ChangeHash], ours: &[ChangeHash]) -> bool { diff --git a/src/simtest/bench.rs b/src/simtest/bench.rs index 39e054e..aec54dc 100644 --- a/src/simtest/bench.rs +++ b/src/simtest/bench.rs @@ -350,6 +350,7 @@ impl Totals { relay.asked_known_later += stats.relay.asked_known_later; relay.nothing_new_change_bytes += stats.relay.nothing_new_change_bytes; relay.nothing_new_bloom_bytes += stats.relay.nothing_new_bloom_bytes; + relay.waiting += stats.relay.waiting; relay.off_tree += stats.relay.off_tree; relay.off_tree_ms += stats.relay.off_tree_ms; for (_, peer_state) in node.peers_snapshot().await { @@ -717,13 +718,14 @@ fn report( ); let _ = writeln!( out, - " sync per write: {:.1} taken as delivered ({:.0} bytes each), {:.1} with only changes the receiver had ({:.0} bytes each; {:.1} along trees, {:.1} taken as delivered)", + " sync per write: {:.1} taken as delivered ({:.0} bytes each), {:.1} with only changes the receiver had ({:.0} bytes each; {:.1} along trees, {:.1} taken as delivered), {:.1} with changes waiting for predecessors", delivered.0, delivered.1, nothing_new.0, nothing_new.1, (r1.nothing_new_along_trees - r0.nothing_new_along_trees) as f64 / writes, (r1.nothing_new_delivered - r0.nothing_new_delivered) as f64 / writes, + (r1.waiting - r0.waiting) as f64 / writes, ); let each = |bytes: u64, count: u64| { if count > 0 { diff --git a/src/swarm/stats.rs b/src/swarm/stats.rs index 31b6a3b..ba75d68 100644 --- a/src/swarm/stats.rs +++ b/src/swarm/stats.rs @@ -157,6 +157,10 @@ pub(crate) struct Relay { pub(crate) carrying_bloom_bytes: u64, pub(crate) nothing_new_change_bytes: u64, pub(crate) nothing_new_bloom_bytes: u64, + /// Sync messages received that carried changes, none of them applied yet: they wait + /// for predecessors that didn't come (Automerge holds them back). Not counted with + /// `nothing_new`, which they aren't. + pub(crate) waiting: u64, /// The changes new to us that sync messages brought (beside what they carried that /// we had): with `carrying` less `nothing_new`, how many a message with news brings. pub(crate) new_changes: u64, @@ -314,6 +318,11 @@ impl NodeStats { inner.relay.nothing_new_bloom_bytes += sizes.1 as u64; } + /// A sync message carried changes that wait for predecessors (`Relay::waiting`). + pub(crate) fn waiting(&self) { + self.lock().relay.waiting += 1; + } + /// Changes from elsewhere were applied, `delays_ms` after they were committed, that /// came not along a writer's tree. pub(crate) fn off_tree(&self, delays_ms: &[i64]) { diff --git a/src/swarm/sync.rs b/src/swarm/sync.rs index 0edc277..5511ea2 100644 --- a/src/swarm/sync.rs +++ b/src/swarm/sync.rs @@ -574,6 +574,7 @@ pub(crate) async fn handle_sync( peer_state.last_heads = Some((epoch, transport::HeadsSummary::of(message.heads().to_vec()))); let heads_before = replica.heads().to_vec(); + let said = message.heads().to_vec(); let sync = &mut peer_state.sync; replica @@ -617,7 +618,13 @@ pub(crate) async fn handle_sync( stats.new_changes(new); } if carried && !changed { - stats.nothing_new(payload_len, writer.is_some(), delivered, sizes); + // Its heads not ours yet: what it brought waits for predecessors, which + // isn't having come twice. + if !crate::document::changes::holds_all(&replica.doc, &said) { + stats.waiting(); + } else { + stats.nothing_new(payload_len, writer.is_some(), delivered, sizes); + } } // Whose they are, to pass them on along their writers' trees, however they // came (along a tree, with an answer, pulled). -- cgit v1.3.1