aboutsummaryrefslogtreecommitdiffstats
path: root/src/document/sync.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/document/sync.rs')
-rw-r--r--src/document/sync.rs86
1 files changed, 86 insertions, 0 deletions
diff --git a/src/document/sync.rs b/src/document/sync.rs
index e93fa17..8265a02 100644
--- a/src/document/sync.rs
+++ b/src/document/sync.rs
@@ -103,6 +103,13 @@ pub(crate) struct SyncSession {
/// (`withdraw_for_delta`): no more messages until it answers, or the answer is
/// overdue (then the offer goes out again).
pub(crate) delta_offered: bool,
+ /// The delta last offered: its heads (sorted, as `DeltaSpec`), and the size of the
+ /// message it replaced (`offer_again`).
+ last_offer: Option<(
+ Vec<crate::document::ChangeHash>,
+ Vec<crate::document::ChangeHash>,
+ usize,
+ )>,
/// What the sync state knew of the peer before the last message from it
/// (`answered_as_delivered`).
before_receive: Option<PeerView>,
@@ -146,6 +153,7 @@ impl SyncSession {
retry_at: None,
send_backoff: Backoff::new(SEND_RETRY, SEND_RETRY_MAX),
delta_offered: false,
+ last_offer: None,
before_receive: None,
before_generate: (Vec::new(), false),
reply: None,
@@ -291,6 +299,51 @@ impl SyncSession {
self.state.their_heads.clone().unwrap_or_default()
}
+ /// We offered the delta from `from` to `to` in place of a message of `bytes`.
+ pub(crate) fn offered(
+ &mut self,
+ from: &[crate::document::ChangeHash],
+ to: &[crate::document::ChangeHash],
+ bytes: usize,
+ ) {
+ self.last_offer = Some((from.to_vec(), to.to_vec(), bytes));
+ }
+
+ /// The delta to offer again, if nothing changed since the last offer (`offered`):
+ /// the peer's heads as we know them, our `heads`, and the message too large for
+ /// `threshold` still. Then the message would be the same, and so the delta: its
+ /// `from`, offered as by `withdraw_for_delta`, without generating the message.
+ pub(crate) fn offer_again(
+ &mut self,
+ heads: &[crate::document::ChangeHash],
+ threshold: usize,
+ now: Instant,
+ ) -> Option<Vec<crate::document::ChangeHash>> {
+ let (from, to, bytes) = self.last_offer.as_ref()?;
+ let sorted = |heads: &[crate::document::ChangeHash]| {
+ let mut heads = heads.to_vec();
+ heads.sort_unstable();
+ heads.dedup();
+ heads
+ };
+ let theirs = sorted(self.state.their_heads.as_deref().unwrap_or_default());
+ if *bytes <= threshold || theirs != *from || sorted(heads) != *to {
+ return None;
+ }
+ let from = from.clone();
+ if self.reply.is_none() {
+ self.expect_reply(Vec::new(), now, SyncBytes::default(), 0);
+ }
+ // Handed over like a generated message (`send_failed` then retries).
+ self.sending = Some(Sending {
+ sent: Vec::new(),
+ regenerate: false,
+ assumed: None,
+ });
+ self.delta_offered = true;
+ Some(from)
+ }
+
/// The message just generated, withdrawn for a delta (`withdraw_for_delta`), turns
/// out to carry nothing the peer lacks (it has our heads): none goes, and nothing is
/// offered or awaited.
@@ -783,6 +836,39 @@ mod tests {
assert!(!a.delta_offered);
}
+ /// An offer that lapsed goes again as it was while nothing changed; otherwise, or
+ /// once the message would fit, it is generated afresh.
+ #[test]
+ fn the_same_offer_goes_again_ungenerated() {
+ use crate::document::testing::change;
+ use automerge::{ROOT, transaction::Transactable};
+ let mut ours = Document::new();
+ change(&mut ours, |doc| doc.put(ROOT, "k", 1).unwrap());
+ let heads = ours.get_heads();
+ let mut a = session();
+ a.state.their_heads = Some(Vec::new());
+ let now = Instant::now();
+ assert_eq!(
+ a.offer_again(&heads, 1000, now),
+ None,
+ "nothing offered yet"
+ );
+
+ a.offered(&[], &heads, 5000);
+ assert_eq!(a.offer_again(&heads, 1000, now), Some(Vec::new()));
+ assert!(a.delta_offered && a.reply.is_some() && a.sending.is_some());
+ assert_eq!(a.offer_again(&heads, 5000, now), None, "it would fit now");
+
+ change(&mut ours, |doc| doc.put(ROOT, "k", 2).unwrap());
+ assert_eq!(
+ a.offer_again(&ours.get_heads(), 1000, now),
+ None,
+ "ours moved"
+ );
+ a.state.their_heads = Some(heads.clone());
+ assert_eq!(a.offer_again(&heads, 1000, now), None, "theirs moved");
+ }
+
/// A message too large to send is taken back: the peer, after the offer and an
/// overdue answer, gets the same changes in the next message.
#[test]