aboutsummaryrefslogtreecommitdiffstats
path: root/src/document
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2026-11-04 20:28:14 +0100
committertv <tv@krebsco.de>2026-11-04 20:28:14 +0100
commitcd0c1ef47070059f7223aa86471576988727f167 (patch)
treecc36d6e0b73753689566ffd8ce6628cdb02141c8 /src/document
parenta57716f79c1c7c34916325b494397cc72ae4c7f7 (diff)
delivered means has all up to our heads
Diffstat (limited to 'src/document')
-rw-r--r--src/document/sync.rs72
1 files changed, 59 insertions, 13 deletions
diff --git a/src/document/sync.rs b/src/document/sync.rs
index 14adca7..fda8889 100644
--- a/src/document/sync.rs
+++ b/src/document/sync.rs
@@ -46,12 +46,18 @@ pub(crate) struct Sending {
/// A sync pass was requested meanwhile. Once this one is handed over, the peer must
/// be enqueued again so the deferred changes go out.
pub(crate) regenerate: bool,
- /// Taken as delivered (`assume_delivered`): the peer's heads and the shared heads as
- /// they were before, put back if the send fails.
- pub(crate) assumed: Option<(
- Option<Vec<crate::document::ChangeHash>>,
- Vec<crate::document::ChangeHash>,
- )>,
+ /// Taken as delivered (`assume_delivered`): what we knew of the peer before, put
+ /// back if the send fails.
+ pub(crate) assumed: Option<Assumed>,
+}
+
+/// What `assume_delivered` replaces in the sync state, as it was.
+#[derive(Clone)]
+pub(crate) struct Assumed {
+ their_heads: Option<Vec<crate::document::ChangeHash>>,
+ shared_heads: Vec<crate::document::ChangeHash>,
+ their_have: Option<Vec<sync::Have>>,
+ their_need: Option<Vec<crate::document::ChangeHash>>,
}
/// Session IDs, unique within the process.
@@ -251,12 +257,23 @@ impl SyncSession {
let Some(sending) = &mut self.sending else {
return;
};
- sending.assumed = Some((
- self.state.their_heads.clone(),
- self.state.shared_heads.clone(),
- ));
+ sending.assumed = Some(Assumed {
+ their_heads: self.state.their_heads.clone(),
+ shared_heads: self.state.shared_heads.clone(),
+ their_have: self.state.their_have.clone(),
+ their_need: self.state.their_need.clone(),
+ });
self.state.their_heads = Some(heads.to_vec());
self.state.shared_heads = heads.to_vec();
+ // And it has all up to our heads, needing nothing: the next message carries
+ // what came since, and if what we sent is forgotten (`resend_if_needed`), only
+ // that goes again, not all since the peer's last message, which may be long ago
+ // (no answers come).
+ self.state.their_have = Some(vec![sync::Have {
+ last_sync: heads.to_vec(),
+ bloom: sync::BloomFilter::default(),
+ }]);
+ self.state.their_need = Some(Vec::new());
self.state.in_flight = false;
self.reply = None;
}
@@ -372,9 +389,11 @@ impl SyncSession {
for hash in &sending.sent {
self.state.sent_hashes.remove(hash);
}
- if let Some((their_heads, shared_heads)) = sending.assumed {
- self.state.their_heads = their_heads;
- self.state.shared_heads = shared_heads;
+ if let Some(assumed) = sending.assumed {
+ self.state.their_heads = assumed.their_heads;
+ self.state.shared_heads = assumed.shared_heads;
+ self.state.their_have = assumed.their_have;
+ self.state.their_need = assumed.their_need;
}
if let Some(wait) = &mut self.reply {
wait.sent.retain(|hash| !sending.sent.contains(hash));
@@ -769,6 +788,33 @@ mod tests {
assert_eq!(ours.get_heads(), theirs.get_heads());
}
+ /// After messages taken as delivered (no answers, so nothing new heard of what the
+ /// peer has), forgetting what was sent resends only what came since the last of
+ /// them, not all since the peer's last message.
+ #[test]
+ fn after_delivered_messages_only_news_goes_again() {
+ use crate::document::testing::change;
+ use automerge::{ROOT, transaction::Transactable};
+ let (mut ours, mut theirs) = (Document::new(), Document::new());
+ change(&mut ours, |doc| doc.put(ROOT, "early", 1).unwrap());
+ let (mut a, mut b) = in_step(&mut ours, &mut theirs);
+ let (now, bytes) = (Instant::now(), SyncBytes::default());
+ for i in 0..5 {
+ change(&mut ours, |doc| doc.put(ROOT, format!("k{i}"), i).unwrap());
+ let message = a.generate(&ours, now, bytes).unwrap();
+ a.assume_delivered(&ours.get_heads());
+ a.send_succeeded(a.id());
+ b.receive(&mut theirs, decode(&message).unwrap()).unwrap();
+ assert!(b.answered_as_delivered(&theirs.get_heads()));
+ }
+ // As `resend_if_needed` does when the peer needs what we sent.
+ a.state.sent_hashes.clear();
+ change(&mut ours, |doc| doc.put(ROOT, "news", 6).unwrap());
+ let message = a.generate(&ours, now, bytes).unwrap();
+ let carried = sync::Message::decode(&message.0).unwrap().changes.len();
+ assert_eq!(carried, 1, "only the news");
+ }
+
/// Our transport refused a message taken as delivered: the peer's heads are as
/// before, so the next message carries the change again.
#[test]