aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/document/sync.rs17
-rw-r--r--src/simtest/bench.rs4
-rw-r--r--src/swarm/stats.rs9
-rw-r--r--src/swarm/sync.rs9
4 files changed, 36 insertions, 3 deletions
diff --git a/src/document/sync.rs b/src/document/sync.rs
index f9f0295..179df78 100644
--- a/src/document/sync.rs
+++ b/src/document/sync.rs
@@ -700,6 +700,23 @@ impl SyncSession {
}
}
+ /// Whether the peer, as the message just applied says, is behind us: all its heads
+ /// in our history, and not all of ours. Asked before `answered_as_delivered`: a
+ /// message taken as delivered from a peer behind us is answered with what it lacks
+ /// (a node that got its writer's changes before its parent did, by another way).
+ pub(crate) fn peer_behind(
+ &self,
+ doc: &Document,
+ heads: &[crate::document::ChangeHash],
+ ) -> bool {
+ self.state.their_heads.as_ref().is_some_and(|theirs| {
+ theirs
+ .iter()
+ .all(|hash| doc.get_change_by_hash(hash).is_some())
+ && heads.iter().any(|hash| !theirs.contains(hash))
+ })
+ }
+
/// We applied a message its sender took as delivered (`assume_delivered`,
/// `SyncEnvelope::delivered`) to `doc`: if we hold exactly its sender's heads now,
/// Automerge's answer, our heads, would tell it nothing. So none goes: we count as
diff --git a/src/simtest/bench.rs b/src/simtest/bench.rs
index 80c8858..1498317 100644
--- a/src/simtest/bench.rs
+++ b/src/simtest/bench.rs
@@ -346,6 +346,7 @@ impl Totals {
relay.new_changes += stats.relay.new_changes;
relay.asked_known_lately += stats.relay.asked_known_lately;
relay.asked_known_later += stats.relay.asked_known_later;
+ relay.answering_behind += stats.relay.answering_behind;
relay.nothing_new_change_bytes += stats.relay.nothing_new_change_bytes;
relay.waiting += stats.relay.waiting;
relay.off_tree += stats.relay.off_tree;
@@ -782,8 +783,9 @@ fn report(
);
let _ = writeln!(
out,
- " asked for, though counted as the asker's (another parent brings them): {:.2} per write ({lately} within 2 s, {later} later)",
+ " asked for, though counted as the asker's (another parent brings them): {:.2} per write ({lately} within 2 s, {later} later); {:.2} per write taken as delivered by a sender behind the receiver, answered",
(lately + later) as f64 / writes,
+ (r1.answering_behind - r0.answering_behind) as f64 / writes,
);
let (i0, i1) = (during.0.incidents, during.1.incidents);
let _ = writeln!(
diff --git a/src/swarm/stats.rs b/src/swarm/stats.rs
index 02ccad5..218d5e8 100644
--- a/src/swarm/stats.rs
+++ b/src/swarm/stats.rs
@@ -166,6 +166,10 @@ pub(crate) struct Relay {
/// counting them so (so they weren't what we passed on built on), and later.
pub(crate) asked_known_lately: u64,
pub(crate) asked_known_later: u64,
+ /// Messages taken as delivered by their sender, which was behind us: answered with
+ /// what it lacks (it got changes we had along its tree late, or we got them early by
+ /// another way).
+ pub(crate) answering_behind: u64,
/// Changes from elsewhere that came not along a writer's tree (pulled, with an
/// answer, sent again, fetched as a delta), and their delays summed (as `Propagation`).
pub(crate) off_tree: u64,
@@ -312,6 +316,11 @@ impl NodeStats {
inner.relay.nothing_new_change_bytes += change_bytes as u64;
}
+ /// A message taken as delivered came from a peer behind us (`Relay::answering_behind`).
+ pub(crate) fn answering_behind(&self) {
+ self.lock().relay.answering_behind += 1;
+ }
+
/// A sync message carried changes that wait for predecessors (`Relay::waiting`).
pub(crate) fn waiting(&self) {
self.lock().relay.waiting += 1;
diff --git a/src/swarm/sync.rs b/src/swarm/sync.rs
index c969678..5100b98 100644
--- a/src/swarm/sync.rs
+++ b/src/swarm/sync.rs
@@ -603,10 +603,15 @@ pub(crate) async fn handle_sync(
let heads_after = replica.heads().to_vec();
if delivered {
// Level with the sender now, which takes the message as delivered:
- // nothing to answer (`SyncSession::answered_as_delivered`).
- peer_state
+ // nothing to answer (`SyncSession::answered_as_delivered`). Unless it is
+ // behind us: then the answer brings it what it lacks.
+ let behind = peer_state.sync.peer_behind(&replica.doc, &heads_after);
+ let answered = peer_state
.sync
.answered_as_delivered(&replica.doc, &heads_after);
+ if behind && !answered {
+ stats.answering_behind();
+ }
}
stats.heads_seen(heads_after.len());
let changed = heads_after != heads_before;