use itertools::Itertools;
use serde::ser::SerializeMap;
use std::collections::{HashMap, HashSet};
use crate::{
patches::PatchLog,
storage::{parse, ReadChangeOpError},
Automerge, AutomergeError, ChangeHash, ReadDoc,
};
mod bloom;
mod message_builder;
mod state;
use message_builder::MessageBuilder;
#[cfg(test)]
mod v1_compat_test;
pub use bloom::{BloomFilter, DecodeError as DecodeBloomError};
pub use state::DecodeError as DecodeStateError;
pub use state::{Have, State};
pub trait SyncDoc {
fn generate_sync_message(&self, sync_state: &mut State) -> Option<Message>;
fn receive_sync_message(
&mut self,
sync_state: &mut State,
message: Message,
) -> Result<(), AutomergeError>;
fn receive_sync_message_log_patches(
&mut self,
sync_state: &mut State,
message: Message,
patch_log: &mut PatchLog,
) -> Result<(), AutomergeError>;
}
const MESSAGE_TYPE_SYNC: u8 = 0x42; const MESSAGE_TYPE_SYNC_V2: u8 = 0x43;
#[derive(Clone, Debug, PartialEq)]
pub enum MessageVersion {
V1,
V2,
}
impl MessageVersion {
fn parse(input: parse::Input<'_>) -> parse::ParseResult<'_, Self, ReadMessageError> {
let (i, first_byte) = parse::take1(input)?;
match first_byte {
MESSAGE_TYPE_SYNC => Ok((i, Self::V1)),
MESSAGE_TYPE_SYNC_V2 => Ok((i, Self::V2)),
_ => Err(parse::ParseError::Error(ReadMessageError::WrongType {
expected_one_of: vec![MESSAGE_TYPE_SYNC, MESSAGE_TYPE_SYNC_V2],
found: first_byte,
})),
}
}
fn encode(&self) -> u8 {
match self {
Self::V1 => MESSAGE_TYPE_SYNC,
Self::V2 => MESSAGE_TYPE_SYNC_V2,
}
}
}
impl SyncDoc for Automerge {
fn generate_sync_message(&self, sync_state: &mut State) -> Option<Message> {
let our_heads = self.get_heads();
let our_need = if sync_state.read_only {
vec![]
} else {
self.get_missing_deps(sync_state.their_heads.as_ref().unwrap_or(&vec![]))
};
let their_heads_set = if let Some(ref heads) = sync_state.their_heads {
heads.iter().collect::<HashSet<_>>()
} else {
HashSet::new()
};
let our_have = if our_need.iter().all(|hash| their_heads_set.contains(hash)) {
vec![self.make_bloom_filter(sync_state.shared_heads.clone())]
} else {
Vec::new()
};
if let Some(ref their_have) = sync_state.their_have {
if let Some(first_have) = their_have.first().as_ref() {
if !first_have
.last_sync
.iter()
.all(|hash| self.has_change(hash))
{
return Some(Message::reset(our_heads));
}
}
}
let message_builder = if sync_state.is_peer_read_only() {
MessageBuilder::new(vec![], sync_state)
} else if let Some((their_have, their_need)) = sync_state.their() {
if sync_state.send_doc() {
let hashes = self.change_graph.get_hashes(&[]);
MessageBuilder::new_v2(self.save(), hashes)
} else {
let all_hashes = self
.get_hashes_to_send(their_have, their_need)
.expect("Should have only used hashes that are in the document");
let hashes: Vec<_> = all_hashes
.into_iter()
.filter(|hash| !sync_state.sent_hashes.contains(hash))
.collect();
if hashes.len() > self.change_graph.len() / 3 && sync_state.supports_v2_messages() {
let all_hashes = self.change_graph.get_hashes(&[]);
MessageBuilder::new_v2(self.save(), all_hashes)
} else {
let changes = self.get_changes_by_hashes(hashes.iter().copied()).ok()?;
MessageBuilder::new(changes, sync_state)
}
}
} else {
MessageBuilder::new(vec![], sync_state)
};
let heads_unchanged = sync_state.last_sent_heads == our_heads;
let heads_equal = sync_state.their_heads.as_ref() == Some(&our_heads);
if heads_unchanged && sync_state.have_responded {
if (heads_equal || sync_state.read_only) && message_builder.is_empty() {
return None;
}
if sync_state.in_flight {
return None;
}
}
sync_state.have_responded = true;
sync_state.last_sent_heads.clone_from(&our_heads);
sync_state.sent_hashes.extend(message_builder.hashes());
let mut flags = MessageFlags::new();
flags.set(MessageFlags::SUPPORTS_SYNC_RESET);
if sync_state.read_only {
flags.set(MessageFlags::READ_ONLY);
}
let heads_to_send = if sync_state.needs_reset {
sync_state.needs_reset = false;
if sync_state.peer_supports_sync_reset() {
flags.set(MessageFlags::SYNC_RESET);
our_heads.clone()
} else {
vec![]
}
} else {
our_heads.clone()
};
let sync_message = message_builder
.heads(heads_to_send)
.have(our_have)
.need(our_need)
.flags(Some(flags))
.build();
sync_state.in_flight = true;
Some(sync_message)
}
fn receive_sync_message(
&mut self,
sync_state: &mut State,
message: Message,
) -> Result<(), AutomergeError> {
let mut patch_log = PatchLog::inactive();
self.receive_sync_message_inner(sync_state, message, &mut patch_log)
}
fn receive_sync_message_log_patches(
&mut self,
sync_state: &mut State,
message: Message,
patch_log: &mut PatchLog,
) -> Result<(), AutomergeError> {
self.receive_sync_message_inner(sync_state, message, patch_log)
}
}
impl Automerge {
#[inline(never)]
fn make_bloom_filter(&self, last_sync: Vec<ChangeHash>) -> Have {
let hashes = self.change_graph.get_hashes(&last_sync);
Have {
last_sync,
bloom: BloomFilter::from_hashes(hashes.iter()),
}
}
#[inline(never)]
fn get_hashes_to_send(
&self,
have: &[Have],
need: &[ChangeHash],
) -> Result<Vec<ChangeHash>, AutomergeError> {
if have.is_empty() {
Ok(need.to_vec())
} else {
let mut last_sync_hashes = HashSet::new();
let mut bloom_filters = Vec::with_capacity(have.len());
for h in have {
let Have { last_sync, bloom } = h;
last_sync_hashes.extend(last_sync);
bloom_filters.push(bloom);
}
let last_sync_hashes = last_sync_hashes.into_iter().copied().collect::<Vec<_>>();
let hashes = self.change_graph.get_hashes(&last_sync_hashes);
let mut change_hashes = HashSet::with_capacity(hashes.len());
let mut dependents: HashMap<ChangeHash, Vec<ChangeHash>> = HashMap::new();
let mut hashes_to_send = HashSet::new();
for hash in &*hashes {
change_hashes.insert(*hash);
for dep in self.change_graph.deps(hash) {
dependents.entry(dep).or_default().push(*hash);
}
if bloom_filters.iter().all(|bloom| !bloom.contains_hash(hash)) {
hashes_to_send.insert(*hash);
}
}
let mut stack = hashes_to_send.iter().copied().collect::<Vec<_>>();
while let Some(hash) = stack.pop() {
if let Some(deps) = dependents.get(&hash) {
for dep in deps {
if hashes_to_send.insert(*dep) {
stack.push(*dep);
}
}
}
}
let mut final_hashes = Vec::with_capacity(hashes_to_send.len() + need.len());
for hash in need {
if !hashes_to_send.contains(hash) {
final_hashes.push(*hash);
}
}
for hash in &*hashes {
if hashes_to_send.contains(hash) {
final_hashes.push(*hash);
}
}
Ok(final_hashes)
}
}
#[inline(never)]
pub(crate) fn receive_sync_message_inner(
&mut self,
sync_state: &mut State,
message: Message,
patch_log: &mut PatchLog,
) -> Result<(), AutomergeError> {
sync_state.in_flight = false;
let before_heads = self.get_heads();
let Message {
heads: message_heads,
changes: message_changes,
need: message_need,
have: message_have,
flags: message_flags,
..
} = message;
if let Some(flags) = message_flags {
let mut caps = vec![Capability::MessageV2];
if flags.contains(MessageFlags::SUPPORTS_SYNC_RESET) {
caps.push(Capability::SyncReset);
}
sync_state.their_capabilities = Some(caps);
if flags.contains(MessageFlags::SYNC_RESET) {
sync_state.sent_hashes.clear();
}
sync_state.peer_read_only = flags.contains(MessageFlags::READ_ONLY);
}
let changes_is_empty = message_changes.is_empty();
if !changes_is_empty && !sync_state.read_only {
self.load_incremental_log_patches(&message_changes.join(), patch_log)?;
sync_state.shared_heads = advance_heads(
&before_heads.iter().collect(),
&self.get_heads().into_iter().collect(),
&sync_state.shared_heads,
);
}
self.filter_changes(&message_heads, &mut sync_state.sent_hashes)?;
if changes_is_empty && message_heads == before_heads {
sync_state.last_sent_heads.clone_from(&message_heads);
}
let known_heads = message_heads
.iter()
.filter(|head| self.has_change(head))
.collect::<Vec<_>>();
if known_heads.len() == message_heads.len() {
sync_state.shared_heads.clone_from(&message_heads);
if message_heads.is_empty() {
sync_state.last_sent_heads = Default::default();
sync_state.sent_hashes = Default::default();
}
} else {
sync_state.shared_heads = sync_state
.shared_heads
.iter()
.chain(known_heads)
.copied()
.unique()
.sorted()
.collect::<Vec<_>>();
}
sync_state.their_have = Some(message_have);
sync_state.their_heads = Some(message_heads);
sync_state.their_need = Some(message_need);
Ok(())
}
}
#[derive(Debug, thiserror::Error)]
pub enum ReadMessageError {
#[error("expected {expected_one_of:?} but found {found}")]
WrongType { expected_one_of: Vec<u8>, found: u8 },
#[error("{0}")]
Parse(String),
#[error(transparent)]
ReadChangeOps(#[from] ReadChangeOpError),
#[error("not enough input")]
NotEnoughInput,
}
impl From<parse::leb128::Error> for ReadMessageError {
fn from(e: parse::leb128::Error) -> Self {
ReadMessageError::Parse(e.to_string())
}
}
impl From<bloom::ParseError> for ReadMessageError {
fn from(e: bloom::ParseError) -> Self {
ReadMessageError::Parse(e.to_string())
}
}
impl From<crate::storage::change::ParseError> for ReadMessageError {
fn from(e: crate::storage::change::ParseError) -> Self {
ReadMessageError::Parse(format!("error parsing changes: {}", e))
}
}
impl From<ReadMessageError> for parse::ParseError<ReadMessageError> {
fn from(e: ReadMessageError) -> Self {
parse::ParseError::Error(e)
}
}
impl From<parse::ParseError<ReadMessageError>> for ReadMessageError {
fn from(p: parse::ParseError<ReadMessageError>) -> Self {
match p {
parse::ParseError::Error(e) => e,
parse::ParseError::Incomplete(..) => Self::NotEnoughInput,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Message {
pub heads: Vec<ChangeHash>,
pub need: Vec<ChangeHash>,
pub have: Vec<Have>,
pub changes: ChunkList,
pub flags: Option<MessageFlags>,
pub version: MessageVersion,
}
#[derive(Clone, Debug, PartialEq)]
pub struct ChunkList(Vec<Vec<u8>>);
impl From<Vec<Vec<u8>>> for ChunkList {
fn from(v: Vec<Vec<u8>>) -> Self {
Self(v)
}
}
impl From<Vec<u8>> for ChunkList {
fn from(v: Vec<u8>) -> Self {
Self(vec![v])
}
}
impl ChunkList {
fn parse(i: parse::Input<'_>) -> parse::ParseResult<'_, Self, ReadMessageError> {
let change_parser = |i| {
let (i, bytes) = parse::length_prefixed_bytes(i)?;
Ok((i, bytes.to_vec()))
};
let (i, stored_changes) = parse::length_prefixed(change_parser)(i)?;
Ok((i, Self(stored_changes)))
}
pub fn empty() -> Self {
Self(Vec::new())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn iter(&self) -> impl ExactSizeIterator<Item = &[u8]> {
self.0.iter().map(|v| v.as_slice())
}
pub(crate) fn join(&self) -> Vec<u8> {
let total: usize = self.0.iter().map(Vec::len).sum();
let mut result = Vec::with_capacity(total);
for v in &self.0 {
result.extend(v);
}
result
}
}
impl serde::Serialize for Message {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut map = serializer.serialize_map(Some(4))?;
map.serialize_entry("heads", &self.heads)?;
map.serialize_entry("need", &self.need)?;
map.serialize_entry("have", &self.have)?;
map.serialize_entry("changes", &self.changes.0)?;
map.end()
}
}
fn parse_have(input: parse::Input<'_>) -> parse::ParseResult<'_, Have, ReadMessageError> {
let (i, last_sync) = parse::length_prefixed(parse::change_hash)(input)?;
let (i, bloom_bytes) = parse::length_prefixed_bytes(i)?;
let (_, bloom) = BloomFilter::parse(parse::Input::new(bloom_bytes)).map_err(|e| e.lift())?;
Ok((i, Have { last_sync, bloom }))
}
impl Message {
pub(crate) fn reset(our_heads: Vec<ChangeHash>) -> Message {
Message {
heads: our_heads,
need: Vec::new(),
have: vec![Have::default()],
changes: ChunkList::empty(),
flags: {
let mut f = MessageFlags::new();
f.set(MessageFlags::SUPPORTS_SYNC_RESET);
Some(f)
},
version: MessageVersion::V1,
}
}
pub fn decode(input: &[u8]) -> Result<Self, ReadMessageError> {
let input = parse::Input::new(input);
match Self::parse(input) {
Ok((_, msg)) => Ok(msg),
Err(parse::ParseError::Error(e)) => Err(e),
Err(parse::ParseError::Incomplete(_)) => Err(ReadMessageError::NotEnoughInput),
}
}
pub(crate) fn parse(input: parse::Input<'_>) -> parse::ParseResult<'_, Self, ReadMessageError> {
let (i, message_version) = MessageVersion::parse(input)?;
let (i, heads) = parse::length_prefixed(parse::change_hash)(i)?;
let (i, need) = parse::length_prefixed(parse::change_hash)(i)?;
let (i, have) = parse::length_prefixed(parse_have)(i)?;
let (i, changes) = ChunkList::parse(i)?;
let (i, flags) = if !i.is_empty() {
let (i, raw_bytes) = parse::length_prefixed_bytes(i)?;
(i, Some(MessageFlags::parse_bytes(raw_bytes)))
} else {
(i, None)
};
Ok((
i,
Message {
heads,
need,
have,
changes,
flags,
version: message_version,
},
))
}
pub fn encode(self) -> Vec<u8> {
let mut buf = vec![self.version.encode()];
encode_hashes(&mut buf, &self.heads);
encode_hashes(&mut buf, &self.need);
encode_many(&mut buf, self.have.iter(), |buf, h| {
encode_hashes(buf, &h.last_sync);
leb128::write::unsigned(buf, h.bloom.to_bytes().len() as u64).unwrap();
buf.extend(h.bloom.to_bytes());
});
encode_many(&mut buf, self.changes.iter(), |buf, change| {
leb128::write::unsigned(buf, change.len() as u64).unwrap();
buf.extend::<&[u8]>(change.as_ref())
});
if let Some(flags) = self.flags {
flags.encode(&mut buf);
}
buf
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct MessageFlags(u8);
impl MessageFlags {
pub const SYNC_RESET: u8 = 1 << 0;
pub const READ_ONLY: u8 = 1 << 1;
pub const SUPPORTS_SYNC_RESET: u8 = 1 << 2;
const BITFIELD_MARKER: u8 = 0x80;
const LEGACY_V2_BYTE: u8 = 0x02;
pub fn new() -> Self {
Self(0)
}
pub fn contains(self, flag: u8) -> bool {
self.0 & flag != 0
}
pub fn set(&mut self, flag: u8) {
self.0 |= flag;
}
fn encode(&self, out: &mut Vec<u8>) {
leb128::write::unsigned(out, 2u64).unwrap();
out.push(Self::LEGACY_V2_BYTE);
out.push(Self::BITFIELD_MARKER | self.0);
}
fn parse_bytes(bytes: &[u8]) -> Self {
let mut flags = Self::new();
for &byte in bytes {
if byte & Self::BITFIELD_MARKER != 0 {
flags.0 |= byte & !Self::BITFIELD_MARKER;
}
}
flags
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Capability {
MessageV1,
MessageV2,
SyncReset,
}
fn encode_many<'a, I, It, F>(out: &mut Vec<u8>, data: I, f: F)
where
I: Iterator<Item = It> + ExactSizeIterator + 'a,
F: Fn(&mut Vec<u8>, It),
{
leb128::write::unsigned(out, data.len() as u64).unwrap();
for datum in data {
f(out, datum)
}
}
fn encode_hashes(buf: &mut Vec<u8>, hashes: &[ChangeHash]) {
debug_assert!(
hashes.windows(2).all(|h| h[0] <= h[1]),
"hashes were not sorted"
);
encode_many(buf, hashes.iter(), |buf, hash| buf.extend(hash.as_bytes()))
}
fn advance_heads(
my_old_heads: &HashSet<&ChangeHash>,
my_new_heads: &HashSet<ChangeHash>,
our_old_shared_heads: &[ChangeHash],
) -> Vec<ChangeHash> {
let new_heads = my_new_heads
.iter()
.filter(|head| !my_old_heads.contains(head))
.copied()
.collect::<Vec<_>>();
let common_heads = our_old_shared_heads
.iter()
.filter(|head| my_new_heads.contains(head))
.copied()
.collect::<Vec<_>>();
let mut advanced_heads = HashSet::with_capacity(new_heads.len() + common_heads.len());
for head in new_heads.into_iter().chain(common_heads) {
advanced_heads.insert(head);
}
let mut advanced_heads = advanced_heads.into_iter().collect::<Vec<_>>();
advanced_heads.sort();
advanced_heads
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::parse::Input;
use crate::storage::Chunk;
use crate::transaction::Transactable;
use crate::ActorId;
#[test]
fn encode_decode_empty_message() {
let msg = Message {
heads: vec![],
need: vec![],
have: vec![],
changes: ChunkList::empty(),
flags: None,
version: MessageVersion::V2,
};
let encoded = msg.encode();
Message::parse(Input::new(&encoded)).unwrap();
}
#[test]
fn generate_sync_message_twice_does_nothing() {
let mut doc = crate::AutoCommit::new();
doc.put(crate::ROOT, "key", "value").unwrap();
let mut sync_state = State::new();
assert!(doc.sync().generate_sync_message(&mut sync_state).is_some());
assert!(doc.sync().generate_sync_message(&mut sync_state).is_none());
}
#[test]
fn first_response_is_some_even_if_no_changes() {
let mut doc1 = crate::AutoCommit::new();
doc1.put(crate::ROOT, "key", "value").unwrap();
let mut doc2 = doc1.fork();
let mut s1 = State::new();
let mut s2 = State::new();
let m1 = doc1
.sync()
.generate_sync_message(&mut s1)
.expect("message was none");
doc2.sync().receive_sync_message(&mut s2, m1).unwrap();
let _m2 = doc2
.sync()
.generate_sync_message(&mut s2)
.expect("response was none");
}
#[test]
fn should_not_reply_if_we_have_no_data_after_first_round() {
let mut doc1 = crate::AutoCommit::new();
let mut doc2 = crate::AutoCommit::new();
let mut s1 = State::new();
let mut s2 = State::new();
let m1 = doc1
.sync()
.generate_sync_message(&mut s1)
.expect("message was none");
doc2.sync().receive_sync_message(&mut s2, m1).unwrap();
let _m2 = doc2
.sync()
.generate_sync_message(&mut s2)
.expect("first round message was none");
let m1 = doc1.sync().generate_sync_message(&mut s1);
assert!(m1.is_none());
let m2 = doc2.sync().generate_sync_message(&mut s2);
assert!(m2.is_none());
}
#[test]
fn should_allow_simultaneous_messages_during_synchronisation() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut s1 = State::new();
let mut s2 = State::new();
for i in 0..5 {
doc1.put(&crate::ROOT, "x", i).unwrap();
doc1.commit();
doc2.put(&crate::ROOT, "y", i).unwrap();
doc2.commit();
}
let head1 = doc1.get_heads()[0];
let head2 = doc2.get_heads()[0];
let msg1to2 = doc1
.sync()
.generate_sync_message(&mut s1)
.expect("initial sync from 1 to 2 was None");
let msg2to1 = doc2
.sync()
.generate_sync_message(&mut s2)
.expect("initial sync message from 2 to 1 was None");
let Message {
changes: changes1to2,
..
} = &msg1to2;
assert_eq!(changes1to2.len(), 0);
assert_eq!(msg1to2.have[0].last_sync.len(), 0);
let Message {
changes: changes2to1,
..
} = &msg2to1;
assert_eq!(changes2to1.len(), 0);
assert_eq!(msg2to1.have[0].last_sync.len(), 0);
doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap();
doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap();
let msg1to2 = doc1
.sync()
.generate_sync_message(&mut s1)
.expect("first reply from 1 to 2 was None");
let Message {
changes: changes1to2,
..
} = &msg1to2;
assert!(!changes1to2.is_empty());
let msg2to1 = doc2
.sync()
.generate_sync_message(&mut s2)
.expect("first reply from 2 to 1 was None");
let Message {
changes: changes2to1,
..
} = &msg2to1;
assert!(!changes2to1.is_empty());
doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap();
assert_eq!(doc1.get_missing_deps(&[]), Vec::new());
doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap();
assert_eq!(doc2.get_missing_deps(&[]), Vec::new());
let msg1to2 = doc1
.sync()
.generate_sync_message(&mut s1)
.expect("second reply from 1 to 2 was None");
let Message {
changes: changes1to2,
..
} = &msg1to2;
assert_eq!(changes1to2.len(), 0);
let msg2to1 = doc2
.sync()
.generate_sync_message(&mut s2)
.expect("second reply from 2 to 1 was None");
let Message {
changes: changes2to1,
..
} = &msg2to1;
assert_eq!(changes2to1.len(), 0);
doc1.sync().receive_sync_message(&mut s1, msg2to1).unwrap();
doc2.sync().receive_sync_message(&mut s2, msg1to2).unwrap();
assert_eq!(s1.shared_heads, s2.shared_heads);
assert!(doc1.sync().generate_sync_message(&mut s1).is_none());
assert!(doc2.sync().generate_sync_message(&mut s2).is_none());
doc1.put(crate::ROOT, "x", 5).unwrap();
doc1.commit();
let msg1to2 = doc1
.sync()
.generate_sync_message(&mut s1)
.expect("third reply from 1 to 2 was None");
let mut expected_heads = vec![head1, head2];
expected_heads.sort();
let mut actual_heads = msg1to2.have[0].last_sync.clone();
actual_heads.sort();
assert_eq!(actual_heads, expected_heads);
}
#[test]
fn should_handle_false_positive_head() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut s1 = State::new();
let mut s2 = State::new();
for i in 0..10 {
doc1.put(crate::ROOT, "x", i).unwrap();
doc1.commit();
}
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
let mut i = 0;
let (mut doc1, mut doc2) = loop {
let mut doc1copy = doc1
.clone()
.with_actor(ActorId::try_from("01234567").unwrap());
let val1 = format!("{} @ n1", i);
doc1copy.put(crate::ROOT, "x", val1).unwrap();
doc1copy.commit();
let mut doc2copy = doc1
.clone()
.with_actor(ActorId::try_from("89abcdef").unwrap());
let val2 = format!("{} @ n2", i);
doc2copy.put(crate::ROOT, "x", val2).unwrap();
doc2copy.commit();
let n1_bloom = BloomFilter::from_hashes(doc1copy.get_heads().into_iter());
if n1_bloom.contains_hash(&doc2copy.get_heads()[0]) {
break (doc1copy, doc2copy);
}
i += 1;
};
let mut all_heads = doc1.get_heads();
all_heads.extend(doc2.get_heads());
all_heads.sort();
let (_, mut s1) = State::parse(Input::new(s1.encode().as_slice())).unwrap();
let (_, mut s2) = State::parse(Input::new(s2.encode().as_slice())).unwrap();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert_eq!(doc1.get_heads(), all_heads);
assert_eq!(doc2.get_heads(), all_heads);
}
#[test]
fn should_handle_chains_of_false_positives() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut s1 = State::new();
let mut s2 = State::new();
for i in 0..10 {
doc1.put(crate::ROOT, "x", i).unwrap();
doc1.commit();
}
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
doc1.put(crate::ROOT, "x", 5).unwrap();
doc1.commit();
let bloom = BloomFilter::from_hashes(doc1.get_heads().into_iter());
let mut i = 0;
let mut doc2 = loop {
let mut doc = doc2
.fork()
.with_actor(ActorId::try_from("89abcdef").unwrap());
doc.put(crate::ROOT, "x", format!("{} at 89abdef", i))
.unwrap();
doc.commit();
if bloom.contains_hash(&doc.get_heads()[0]) {
break doc;
}
i += 1;
};
i = 0;
let mut doc2 = loop {
let mut doc = doc2
.fork()
.with_actor(ActorId::try_from("89abcdef").unwrap());
doc.put(crate::ROOT, "x", format!("{} again", i)).unwrap();
doc.commit();
if bloom.contains_hash(&doc.get_heads()[0]) {
break doc;
}
i += 1;
};
doc2.put(crate::ROOT, "x", "final @ 89abcdef").unwrap();
let mut all_heads = doc1.get_heads();
all_heads.extend(doc2.get_heads());
all_heads.sort();
let (_, mut s1) = State::parse(Input::new(s1.encode().as_slice())).unwrap();
let (_, mut s2) = State::parse(Input::new(s2.encode().as_slice())).unwrap();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert_eq!(doc1.get_heads(), all_heads);
assert_eq!(doc2.get_heads(), all_heads);
}
#[test]
fn should_handle_lots_of_branching_and_merging() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("01234567").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("89abcdef").unwrap());
let mut doc3 = crate::AutoCommit::new().with_actor(ActorId::try_from("fedcba98").unwrap());
let mut s1 = State::new();
let mut s2 = State::new();
doc1.put(crate::ROOT, "x", 0).unwrap();
let change1 = doc1.get_last_local_change().unwrap().clone();
doc2.apply_changes([change1.clone()]).unwrap();
doc3.apply_changes([change1]).unwrap();
doc3.put(crate::ROOT, "x", 1).unwrap();
for i in 1..20 {
doc1.put(crate::ROOT, "n1", i).unwrap();
doc2.put(crate::ROOT, "n2", i).unwrap();
let change1 = doc1.get_last_local_change().unwrap().clone();
let change2 = doc2.get_last_local_change().unwrap().clone();
doc1.apply_changes([change2.clone()]).unwrap();
doc2.apply_changes([change1]).unwrap();
}
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
let change3 = doc3.get_last_local_change().unwrap().clone();
doc2.apply_changes([change3]).unwrap();
doc1.put(crate::ROOT, "n1", "final").unwrap();
doc2.put(crate::ROOT, "n1", "final").unwrap();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert_eq!(doc1.get_heads(), doc2.get_heads());
}
#[test]
fn in_flight_logic_should_not_sabotage_concurrent_changes() {
for _ in 0..300 {
let mut doc1 = crate::AutoCommit::new();
let mut doc2 = crate::AutoCommit::new();
let mut s1 = State::new();
let mut s2 = State::new();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
doc2.put(crate::ROOT, "x", 0).unwrap();
let msg = doc2.sync().generate_sync_message(&mut s2).unwrap();
doc1.sync().receive_sync_message(&mut s1, msg).unwrap();
doc1.put(crate::ROOT, "x", 1).unwrap();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert_eq!(doc1.get_heads(), doc2.get_heads());
}
}
fn sync(
a: &mut crate::AutoCommit,
b: &mut crate::AutoCommit,
a_sync_state: &mut State,
b_sync_state: &mut State,
) {
const MAX_ITER: usize = 10;
let mut iterations = 0;
loop {
let a_to_b = a.sync().generate_sync_message(a_sync_state);
let b_to_a = b.sync().generate_sync_message(b_sync_state);
if a_to_b.is_none() && b_to_a.is_none() {
break;
}
if iterations > MAX_ITER {
panic!("failed to sync in {} iterations", MAX_ITER);
}
if let Some(msg) = a_to_b {
b.sync().receive_sync_message(b_sync_state, msg).unwrap()
}
if let Some(msg) = b_to_a {
a.sync().receive_sync_message(a_sync_state, msg).unwrap()
}
iterations += 1;
}
}
#[test]
fn if_first_message_has_no_heads_and_supports_v2_message_send_whole_doc() {
let mut doc1 = crate::AutoCommit::new();
let mut doc2 = crate::AutoCommit::new();
doc2.put(crate::ROOT, "foo", "bar").unwrap();
let mut s1 = State::new();
let mut s2 = State::new();
let outgoing = doc1
.sync()
.generate_sync_message(&mut s1)
.expect("message was none");
doc2.sync().receive_sync_message(&mut s2, outgoing).unwrap();
let response = doc2
.sync()
.generate_sync_message(&mut s2)
.expect("response was none");
let Message { changes, .. } = response;
let (_, chunk) = Chunk::parse(Input::new(&changes.0[0])).unwrap();
assert!(matches!(chunk, Chunk::Document(_)));
}
#[test]
fn read_only_sync_does_not_apply_incoming_changes() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
doc1.put(crate::ROOT, "from_doc1", "hello").unwrap();
doc1.commit();
doc2.put(crate::ROOT, "from_doc2", "world").unwrap();
doc2.commit();
let doc1_heads_before = doc1.get_heads();
let doc2_heads_before = doc2.get_heads();
let mut s1 = State::new_read_only();
let mut s2 = State::new();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc2.get(crate::ROOT, "from_doc1").unwrap().is_some());
assert!(doc2.get(crate::ROOT, "from_doc2").unwrap().is_some());
assert!(doc1.get(crate::ROOT, "from_doc1").unwrap().is_some());
assert!(doc1.get(crate::ROOT, "from_doc2").unwrap().is_none());
assert_eq!(doc1.get_heads(), doc1_heads_before);
assert_ne!(doc2.get_heads(), doc2_heads_before);
}
#[test]
fn read_only_empty_peer_syncs_with_data_peer() {
let mut doc1 = crate::AutoCommit::new();
let mut doc2 = crate::AutoCommit::new();
doc2.put(crate::ROOT, "key", "value").unwrap();
doc2.commit();
let mut s1 = State::new_read_only();
let mut s2 = State::new();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc1.get(crate::ROOT, "key").unwrap().is_none());
assert!(doc1.get_heads().is_empty());
assert!(doc2.get(crate::ROOT, "key").unwrap().is_some());
}
#[test]
fn both_peers_read_only() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
doc1.put(crate::ROOT, "from_doc1", "hello").unwrap();
doc1.commit();
doc2.put(crate::ROOT, "from_doc2", "world").unwrap();
doc2.commit();
let doc1_heads = doc1.get_heads();
let doc2_heads = doc2.get_heads();
let mut s1 = State::new_read_only();
let mut s2 = State::new_read_only();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc1.get(crate::ROOT, "from_doc2").unwrap().is_none());
assert!(doc2.get(crate::ROOT, "from_doc1").unwrap().is_none());
assert_eq!(doc1.get_heads(), doc1_heads);
assert_eq!(doc2.get_heads(), doc2_heads);
}
#[test]
fn both_peers_read_only_converges_to_none() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
doc1.put(crate::ROOT, "from_doc1", "hello").unwrap();
doc1.commit();
doc2.put(crate::ROOT, "from_doc2", "world").unwrap();
doc2.commit();
let mut s1 = State::new_read_only();
let mut s2 = State::new_read_only();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc1.sync().generate_sync_message(&mut s1).is_none());
assert!(doc2.sync().generate_sync_message(&mut s2).is_none());
assert!(s1.is_peer_read_only());
assert!(s2.is_peer_read_only());
}
#[test]
fn both_read_only_one_makes_local_changes() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut s1 = State::new_read_only();
let mut s2 = State::new_read_only();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
doc1.put(crate::ROOT, "key", "value1").unwrap();
doc1.commit();
let doc1_heads_after = doc1.get_heads();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert_eq!(s2.their_heads.as_ref().unwrap(), &doc1_heads_after);
assert!(doc2.get(crate::ROOT, "key").unwrap().is_none());
doc1.put(crate::ROOT, "key", "value2").unwrap();
doc1.commit();
let doc1_heads_after2 = doc1.get_heads();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert_eq!(s2.their_heads.as_ref().unwrap(), &doc1_heads_after2);
assert!(doc2.get(crate::ROOT, "key").unwrap().is_none());
assert!(doc1.sync().generate_sync_message(&mut s1).is_none());
assert!(doc2.sync().generate_sync_message(&mut s2).is_none());
}
#[test]
fn both_read_only_both_make_local_changes() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut s1 = State::new_read_only();
let mut s2 = State::new_read_only();
for round in 0..5 {
doc1.put(crate::ROOT, "doc1_counter", round as i64).unwrap();
doc1.commit();
doc2.put(crate::ROOT, "doc2_counter", round as i64).unwrap();
doc2.commit();
let doc1_heads = doc1.get_heads();
let doc2_heads = doc2.get_heads();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert_eq!(
s1.their_heads.as_ref().unwrap(),
&doc2_heads,
"round {round}: doc1 should know doc2's heads"
);
assert_eq!(
s2.their_heads.as_ref().unwrap(),
&doc1_heads,
"round {round}: doc2 should know doc1's heads"
);
assert!(
doc1.get(crate::ROOT, "doc2_counter").unwrap().is_none(),
"round {round}: doc1 should not have doc2's changes"
);
assert!(
doc2.get(crate::ROOT, "doc1_counter").unwrap().is_none(),
"round {round}: doc2 should not have doc1's changes"
);
assert!(
doc1.sync().generate_sync_message(&mut s1).is_none(),
"round {round}: doc1 should have nothing more to send"
);
assert!(
doc2.sync().generate_sync_message(&mut s2).is_none(),
"round {round}: doc2 should have nothing more to send"
);
}
}
#[test]
fn both_read_only_simultaneous_changes_during_sync() {
for _ in 0..100 {
let mut doc1 =
crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 =
crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut s1 = State::new_read_only();
let mut s2 = State::new_read_only();
doc1.put(crate::ROOT, "x", 1).unwrap();
doc1.commit();
doc2.put(crate::ROOT, "y", 2).unwrap();
doc2.commit();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
doc1.put(crate::ROOT, "x", 3).unwrap();
doc1.commit();
doc2.put(crate::ROOT, "y", 4).unwrap();
doc2.commit();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc1.sync().generate_sync_message(&mut s1).is_none());
assert!(doc2.sync().generate_sync_message(&mut s2).is_none());
assert!(doc1.get(crate::ROOT, "y").unwrap().is_none());
assert!(doc2.get(crate::ROOT, "x").unwrap().is_none());
}
}
#[test]
fn read_only_peer_new_changes_between_sync_rounds() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
doc1.put(crate::ROOT, "round1", "from_doc1").unwrap();
doc1.commit();
doc2.put(crate::ROOT, "round1", "from_doc2").unwrap();
doc2.commit();
let mut s1 = State::new_read_only();
let mut s2 = State::new();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc2.get(crate::ROOT, "round1").unwrap().is_some());
doc1.put(crate::ROOT, "round2", "new_from_doc1").unwrap();
doc1.commit();
doc2.put(crate::ROOT, "round2", "new_from_doc2").unwrap();
doc2.commit();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert_eq!(
doc2.get(crate::ROOT, "round2").unwrap().unwrap().0.to_str(),
doc2.get(crate::ROOT, "round2").unwrap().unwrap().0.to_str(),
);
let all_values: Vec<_> = doc2
.get_all(crate::ROOT, "round2")
.unwrap()
.into_iter()
.map(|(v, _)| v.into_string().unwrap())
.collect();
assert!(all_values.contains(&"new_from_doc1".to_string()));
assert!(all_values.contains(&"new_from_doc2".to_string()));
assert!(
doc1.get(crate::ROOT, "from_doc2").is_err()
|| doc1.get(crate::ROOT, "from_doc2").unwrap().is_none()
);
let doc1_round2_values: Vec<_> = doc1
.get_all(crate::ROOT, "round2")
.unwrap()
.into_iter()
.map(|(v, _)| v.into_string().unwrap())
.collect();
assert_eq!(doc1_round2_values, vec!["new_from_doc1".to_string()]);
}
#[test]
fn read_only_peer_concurrent_changes_during_sync() {
for _ in 0..300 {
let mut doc1 =
crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 =
crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut s1 = State::new_read_only();
let mut s2 = State::new();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
doc2.put(crate::ROOT, "x", 0).unwrap();
let msg = doc2.sync().generate_sync_message(&mut s2).unwrap();
doc1.sync().receive_sync_message(&mut s1, msg).unwrap();
doc1.put(crate::ROOT, "y", 1).unwrap();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc2.get(crate::ROOT, "y").unwrap().is_some());
assert!(doc1.get(crate::ROOT, "x").unwrap().is_none());
}
}
#[test]
fn read_only_publisher_to_multiple_consumers() {
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
r.put(crate::ROOT, "from_r", "hello").unwrap();
r.commit();
let mut sr_a = State::new_read_only();
let mut sa_r = State::new();
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
a.put(crate::ROOT, "from_a", "world").unwrap();
a.commit();
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
let mut sr_b = State::new_read_only();
let mut sb_r = State::new();
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_a").unwrap().is_none());
}
#[test]
fn triangle_changes_arrive_via_two_paths() {
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
r.put(crate::ROOT, "from_r", "hello").unwrap();
r.commit();
a.put(crate::ROOT, "from_a", "world").unwrap();
a.commit();
let mut sr_a = State::new_read_only();
let mut sa_r = State::new();
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
let mut sa_b = State::new();
let mut sb_a = State::new();
sync(&mut a, &mut b, &mut sa_b, &mut sb_a);
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
let mut sr_b = State::new_read_only();
let mut sb_r = State::new();
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
}
#[test]
fn read_only_fully_connected_triangle() {
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
r.put(crate::ROOT, "from_r", "r_val").unwrap();
r.commit();
a.put(crate::ROOT, "from_a", "a_val").unwrap();
a.commit();
b.put(crate::ROOT, "from_b", "b_val").unwrap();
b.commit();
let r_heads = r.get_heads();
let mut sr_a = State::new_read_only();
let mut sa_r = State::new();
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
let mut sr_b = State::new_read_only();
let mut sb_r = State::new();
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
let mut sa_b = State::new();
let mut sb_a = State::new();
sync(&mut a, &mut b, &mut sa_b, &mut sb_a);
assert!(a.get(crate::ROOT, "from_a").unwrap().is_some());
assert!(a.get(crate::ROOT, "from_b").unwrap().is_some());
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_b").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
assert_eq!(a.get_heads(), b.get_heads());
assert_eq!(r.get_heads(), r_heads);
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
assert!(r.get(crate::ROOT, "from_b").unwrap().is_none());
}
#[test]
fn stale_shared_heads_after_read_only_sync() {
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
for i in 0..10 {
r.put(crate::ROOT, "counter", i as i64).unwrap();
r.commit();
}
a.put(crate::ROOT, "from_a", "a_val").unwrap();
a.commit();
let mut sr_a = State::new_read_only();
let mut sa_r = State::new();
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
assert!(a.get(crate::ROOT, "counter").unwrap().is_some());
let mut sa_b = State::new();
let mut sb_a = State::new();
sync(&mut a, &mut b, &mut sa_b, &mut sb_a);
assert!(b.get(crate::ROOT, "counter").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
let mut sr_b = State::new_read_only();
let mut sb_r = State::new();
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
assert!(b.get(crate::ROOT, "counter").unwrap().is_some());
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
}
#[test]
fn read_only_peer_receives_same_changes_from_two_peers() {
let mut r = crate::AutoCommit::new().with_actor(ActorId::try_from("aaaaaa").unwrap());
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("bbbbbb").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("cccccc").unwrap());
r.put(crate::ROOT, "from_r", "r_val").unwrap();
r.commit();
a.put(crate::ROOT, "from_a", "a_val").unwrap();
a.commit();
b.put(crate::ROOT, "from_b", "b_val").unwrap();
b.commit();
let mut sa_b = State::new();
let mut sb_a = State::new();
sync(&mut a, &mut b, &mut sa_b, &mut sb_a);
assert_eq!(a.get_heads(), b.get_heads());
let r_heads = r.get_heads();
let mut sr_a = State::new_read_only();
let mut sa_r = State::new();
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
assert!(a.get(crate::ROOT, "from_r").unwrap().is_some());
assert_eq!(r.get_heads(), r_heads);
let mut sr_b = State::new_read_only();
let mut sb_r = State::new();
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
assert!(b.get(crate::ROOT, "from_r").unwrap().is_some());
assert_eq!(r.get_heads(), r_heads);
assert!(r.get(crate::ROOT, "from_a").unwrap().is_none());
assert!(r.get(crate::ROOT, "from_b").unwrap().is_none());
r.put(crate::ROOT, "from_r_2", "new").unwrap();
r.commit();
sync(&mut r, &mut a, &mut sr_a, &mut sa_r);
assert!(a.get(crate::ROOT, "from_r_2").unwrap().is_some());
sync(&mut r, &mut b, &mut sr_b, &mut sb_r);
assert!(b.get(crate::ROOT, "from_r_2").unwrap().is_some());
}
#[test]
fn switch_read_only_to_read_write_mid_session() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
a.put(crate::ROOT, "from_a", "hello").unwrap();
a.commit();
b.put(crate::ROOT, "from_b", "world").unwrap();
b.commit();
let mut sa = State::new_read_only();
let mut sb = State::new();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
assert!(a.get(crate::ROOT, "from_b").unwrap().is_none());
sa.set_read_only(false);
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "from_b").unwrap().is_some());
assert_eq!(a.get_heads(), b.get_heads());
}
#[test]
fn switch_read_write_to_read_only_mid_session() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
a.put(crate::ROOT, "from_a", "hello").unwrap();
a.commit();
b.put(crate::ROOT, "from_b", "world").unwrap();
b.commit();
let mut sa = State::new();
let mut sb = State::new();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert_eq!(a.get_heads(), b.get_heads());
sa.set_read_only(true);
b.put(crate::ROOT, "new_from_b", "secret").unwrap();
b.commit();
a.put(crate::ROOT, "new_from_a", "published").unwrap();
a.commit();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(b.get(crate::ROOT, "new_from_a").unwrap().is_some());
assert!(a.get(crate::ROOT, "new_from_b").unwrap().is_none());
}
#[test]
fn switch_read_only_to_read_write_with_multiple_rounds() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
a.put(crate::ROOT, "from_a", "initial").unwrap();
a.commit();
let mut sa = State::new_read_only();
let mut sb = State::new();
b.put(crate::ROOT, "round1", "from_b").unwrap();
b.commit();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "round1").unwrap().is_none());
b.put(crate::ROOT, "round2", "from_b").unwrap();
b.commit();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "round2").unwrap().is_none());
b.put(crate::ROOT, "round3", "from_b").unwrap();
b.commit();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "round3").unwrap().is_none());
sa.set_read_only(false);
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "round1").unwrap().is_some());
assert!(a.get(crate::ROOT, "round2").unwrap().is_some());
assert!(a.get(crate::ROOT, "round3").unwrap().is_some());
assert_eq!(a.get_heads(), b.get_heads());
}
#[test]
fn toggle_read_only_multiple_times() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut sa = State::new_read_only();
let mut sb = State::new();
b.put(crate::ROOT, "b1", "val").unwrap();
b.commit();
a.put(crate::ROOT, "a1", "val").unwrap();
a.commit();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(b.get(crate::ROOT, "a1").unwrap().is_some());
assert!(a.get(crate::ROOT, "b1").unwrap().is_none());
sa.set_read_only(false);
b.put(crate::ROOT, "b2", "val").unwrap();
b.commit();
a.put(crate::ROOT, "a2", "val").unwrap();
a.commit();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "b1").unwrap().is_some());
assert!(a.get(crate::ROOT, "b2").unwrap().is_some());
assert!(b.get(crate::ROOT, "a2").unwrap().is_some());
sa.set_read_only(true);
b.put(crate::ROOT, "b3", "val").unwrap();
b.commit();
a.put(crate::ROOT, "a3", "val").unwrap();
a.commit();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(b.get(crate::ROOT, "a3").unwrap().is_some());
assert!(a.get(crate::ROOT, "b3").unwrap().is_none());
sa.set_read_only(false);
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "b3").unwrap().is_some());
assert_eq!(a.get_heads(), b.get_heads());
}
#[test]
fn peer_discovers_remote_read_only_status() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
a.put(crate::ROOT, "from_a", "hello").unwrap();
a.commit();
b.put(crate::ROOT, "from_b", "world").unwrap();
b.commit();
let mut sa = State::new_read_only();
let mut sb = State::new();
assert!(!sb.is_peer_read_only());
let msg = a.sync().generate_sync_message(&mut sa).unwrap();
b.sync().receive_sync_message(&mut sb, msg).unwrap();
assert!(sb.is_peer_read_only());
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(sb.is_peer_read_only());
sa.set_read_only(false);
let msg = a.sync().generate_sync_message(&mut sa).unwrap();
b.sync().receive_sync_message(&mut sb, msg).unwrap();
assert!(!sb.is_peer_read_only());
}
#[test]
fn changes_not_sent_to_read_only_peer() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
b.put(crate::ROOT, "from_b", "world").unwrap();
b.commit();
let mut sa = State::new_read_only();
let mut sb = State::new();
let msg_a = a.sync().generate_sync_message(&mut sa).unwrap();
b.sync().receive_sync_message(&mut sb, msg_a).unwrap();
assert!(sb.is_peer_read_only());
let msg_b = b.sync().generate_sync_message(&mut sb).unwrap();
assert!(msg_b.changes.is_empty());
a.sync().receive_sync_message(&mut sa, msg_b).unwrap();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "from_b").unwrap().is_none());
sa.set_read_only(false);
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "from_b").unwrap().is_some());
assert_eq!(a.get_heads(), b.get_heads());
}
#[test]
fn generate_message_after_set_read_only_even_with_in_flight() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
a.put(crate::ROOT, "from_a", "hello").unwrap();
a.commit();
b.put(crate::ROOT, "from_b", "world").unwrap();
b.commit();
let mut sa = State::new();
let mut sb = State::new();
sync(&mut a, &mut b, &mut sa, &mut sb);
b.put(crate::ROOT, "new_from_b", "secret").unwrap();
b.commit();
let msg_b = b.sync().generate_sync_message(&mut sb).unwrap();
a.sync().receive_sync_message(&mut sa, msg_b).unwrap();
let msg = a.sync().generate_sync_message(&mut sa);
assert!(msg.is_some());
assert!(sa.in_flight);
sa.set_read_only(true);
let msg = a.sync().generate_sync_message(&mut sa);
assert!(
msg.is_some(),
"should generate message after set_read_only even with prior in_flight"
);
let msg = msg.unwrap();
assert!(msg.flags.unwrap().contains(MessageFlags::READ_ONLY));
}
#[test]
fn generate_message_after_set_read_only_false_even_with_in_flight() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
a.put(crate::ROOT, "from_a", "hello").unwrap();
a.commit();
let mut sa = State::new_read_only();
let mut sb = State::new();
sync(&mut a, &mut b, &mut sa, &mut sb);
b.put(crate::ROOT, "from_b", "world").unwrap();
b.commit();
let _msg = a.sync().generate_sync_message(&mut sa);
let msg_b = b.sync().generate_sync_message(&mut sb).unwrap();
a.sync().receive_sync_message(&mut sa, msg_b).unwrap();
let _ = a.sync().generate_sync_message(&mut sa);
sa.set_read_only(false);
let msg = a.sync().generate_sync_message(&mut sa);
assert!(
msg.is_some(),
"should generate message after switching to read-write"
);
let msg = msg.unwrap();
let flags = msg.flags.unwrap();
assert!(flags.contains(MessageFlags::SYNC_RESET));
assert!(!flags.contains(MessageFlags::READ_ONLY));
}
#[test]
fn both_toggle_read_only_to_read_write_simultaneously() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
doc1.put(crate::ROOT, "from_doc1", "hello").unwrap();
doc1.commit();
doc2.put(crate::ROOT, "from_doc2", "world").unwrap();
doc2.commit();
let mut s1 = State::new_read_only();
let mut s2 = State::new_read_only();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc1.get(crate::ROOT, "from_doc2").unwrap().is_none());
assert!(doc2.get(crate::ROOT, "from_doc1").unwrap().is_none());
s1.set_read_only(false);
s2.set_read_only(false);
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc1.get(crate::ROOT, "from_doc2").unwrap().is_some());
assert!(doc2.get(crate::ROOT, "from_doc1").unwrap().is_some());
assert_eq!(doc1.get_heads(), doc2.get_heads());
}
#[test]
fn both_toggle_read_only_to_read_write_with_new_changes() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
doc1.put(crate::ROOT, "original_1", "v1").unwrap();
doc1.commit();
doc2.put(crate::ROOT, "original_2", "v2").unwrap();
doc2.commit();
let mut s1 = State::new_read_only();
let mut s2 = State::new_read_only();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
s1.set_read_only(false);
s2.set_read_only(false);
doc1.put(crate::ROOT, "new_1", "after_switch").unwrap();
doc1.commit();
doc2.put(crate::ROOT, "new_2", "after_switch").unwrap();
doc2.commit();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
assert!(doc1.get(crate::ROOT, "original_2").unwrap().is_some());
assert!(doc1.get(crate::ROOT, "new_2").unwrap().is_some());
assert!(doc2.get(crate::ROOT, "original_1").unwrap().is_some());
assert!(doc2.get(crate::ROOT, "new_1").unwrap().is_some());
assert_eq!(doc1.get_heads(), doc2.get_heads());
}
#[test]
fn both_toggle_after_multiple_read_only_rounds() {
let mut doc1 = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut doc2 = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
let mut s1 = State::new_read_only();
let mut s2 = State::new_read_only();
for i in 0..5 {
doc1.put(crate::ROOT, format!("doc1_r{i}"), i as i64)
.unwrap();
doc1.commit();
doc2.put(crate::ROOT, format!("doc2_r{i}"), i as i64)
.unwrap();
doc2.commit();
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
}
for i in 0..5 {
assert!(doc1
.get(crate::ROOT, format!("doc2_r{i}"))
.unwrap()
.is_none());
assert!(doc2
.get(crate::ROOT, format!("doc1_r{i}"))
.unwrap()
.is_none());
}
s1.set_read_only(false);
s2.set_read_only(false);
sync(&mut doc1, &mut doc2, &mut s1, &mut s2);
for i in 0..5 {
assert!(
doc1.get(crate::ROOT, format!("doc2_r{i}"))
.unwrap()
.is_some(),
"doc1 missing doc2_r{i}"
);
assert!(
doc2.get(crate::ROOT, format!("doc1_r{i}"))
.unwrap()
.is_some(),
"doc2 missing doc1_r{i}"
);
}
assert_eq!(doc1.get_heads(), doc2.get_heads());
}
#[test]
fn switch_to_read_write_with_old_peer() {
let mut a = crate::AutoCommit::new().with_actor(ActorId::try_from("abc123").unwrap());
let mut b = crate::AutoCommit::new().with_actor(ActorId::try_from("def456").unwrap());
a.put(crate::ROOT, "from_a", "hello").unwrap();
a.commit();
b.put(crate::ROOT, "from_b", "world").unwrap();
b.commit();
let mut sa = State::new_read_only();
let mut sb = State::new();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "from_b").unwrap().is_none());
assert!(b.get(crate::ROOT, "from_a").unwrap().is_some());
sa.set_read_only(false);
sa.their_capabilities = None;
let msg = a.sync().generate_sync_message(&mut sa).unwrap();
assert!(msg.heads.is_empty(), "should send empty heads for old peer");
assert!(
!msg.flags.unwrap().contains(MessageFlags::SYNC_RESET),
"should not include SyncReset for old peer"
);
b.sync().receive_sync_message(&mut sb, msg).unwrap();
sync(&mut a, &mut b, &mut sa, &mut sb);
assert!(a.get(crate::ROOT, "from_b").unwrap().is_some());
assert_eq!(a.get_heads(), b.get_heads());
}
}