Skip to content

Commit b72a759

Browse files
authored
fix: register container id err when using shallow snapshot #822 (#823)
* fix: #822 should register container id correctly * chore: changeset
1 parent a53d7c6 commit b72a759

File tree

5 files changed

+114
-25
lines changed

5 files changed

+114
-25
lines changed

.changeset/curvy-maps-sleep.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"loro-crdt": patch
3+
---
4+
5+
fix: register container id err when using shallow snapshot #823

crates/loro-internal/src/arena.rs

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ use crate::{
1414
LoroValue,
1515
};
1616
use append_only_bytes::BytesSlice;
17-
use rustc_hash::FxHashMap;
1817
use loro_common::PeerID;
18+
use rustc_hash::FxHashMap;
1919
use std::fmt;
2020
use std::{
2121
num::NonZeroU16,
@@ -111,10 +111,11 @@ impl ArenaGuards<'_> {
111111
if let Some(d) = get_depth(
112112
p,
113113
&mut self.depth,
114-
&self.parents,
114+
&mut self.parents,
115115
&self.parent_resolver,
116116
&mut self.container_idx_to_id,
117117
&mut self.container_id_to_idx,
118+
&mut self.root_c_idx,
118119
) {
119120
self.depth[child.to_index() as usize] = NonZeroU16::new(d.get() + 1);
120121
} else {
@@ -263,23 +264,26 @@ impl SharedArena {
263264

264265
#[inline]
265266
pub fn set_parent(&self, child: ContainerIdx, parent: Option<ContainerIdx>) {
266-
let parents = &mut self.inner.parents.lock().unwrap();
267+
let mut parents = self.inner.parents.lock().unwrap();
267268
parents.insert(child, parent);
268269
let mut depth = self.inner.depth.lock().unwrap();
270+
let mut root_c_idx = self.inner.root_c_idx.lock().unwrap();
269271

270272
match parent {
271273
Some(p) => {
272274
// Acquire the two maps as mutable guards so we can lazily register
273275
// unknown parents while computing depth.
274276
let mut idx_to_id_guard = self.inner.container_idx_to_id.lock().unwrap();
275277
let mut id_to_idx_guard = self.inner.container_id_to_idx.lock().unwrap();
278+
let parent_resolver_guard = self.inner.parent_resolver.lock().unwrap();
276279
if let Some(d) = get_depth(
277280
p,
278281
&mut depth,
279-
parents,
280-
&self.inner.parent_resolver.lock().unwrap(),
282+
&mut parents,
283+
&parent_resolver_guard,
281284
&mut idx_to_id_guard,
282285
&mut id_to_idx_guard,
286+
&mut root_c_idx,
283287
) {
284288
depth[child.to_index() as usize] = NonZeroU16::new(d.get() + 1);
285289
} else {
@@ -564,21 +568,21 @@ impl SharedArena {
564568

565569
// TODO: this can return a u16 directly now, since the depths are always valid
566570
pub(crate) fn get_depth(&self, container: ContainerIdx) -> Option<NonZeroU16> {
567-
{
568-
let mut depth_guard = self.inner.depth.lock().unwrap();
569-
let parents_guard = self.inner.parents.lock().unwrap();
570-
let resolver_guard = self.inner.parent_resolver.lock().unwrap();
571-
let mut idx_to_id_guard = self.inner.container_idx_to_id.lock().unwrap();
572-
let mut id_to_idx_guard = self.inner.container_id_to_idx.lock().unwrap();
573-
get_depth(
574-
container,
575-
&mut depth_guard,
576-
&parents_guard,
577-
&resolver_guard,
578-
&mut idx_to_id_guard,
579-
&mut id_to_idx_guard,
580-
)
581-
}
571+
let mut depth_guard = self.inner.depth.lock().unwrap();
572+
let mut parents_guard = self.inner.parents.lock().unwrap();
573+
let mut root_c_idx_guard = self.inner.root_c_idx.lock().unwrap();
574+
let resolver_guard = self.inner.parent_resolver.lock().unwrap();
575+
let mut idx_to_id_guard = self.inner.container_idx_to_id.lock().unwrap();
576+
let mut id_to_idx_guard = self.inner.container_id_to_idx.lock().unwrap();
577+
get_depth(
578+
container,
579+
&mut depth_guard,
580+
&mut parents_guard,
581+
&resolver_guard,
582+
&mut idx_to_id_guard,
583+
&mut id_to_idx_guard,
584+
&mut root_c_idx_guard,
585+
)
582586
}
583587

584588
pub(crate) fn iter_value_slice(
@@ -662,10 +666,11 @@ fn _slice_str(range: Range<usize>, s: &mut StrArena) -> String {
662666
fn get_depth(
663667
target: ContainerIdx,
664668
depth: &mut Vec<Option<NonZeroU16>>,
665-
parents: &FxHashMap<ContainerIdx, Option<ContainerIdx>>,
669+
parents: &mut FxHashMap<ContainerIdx, Option<ContainerIdx>>,
666670
parent_resolver: &Option<Arc<ParentResolver>>,
667671
idx_to_id: &mut Vec<ContainerID>,
668672
id_to_idx: &mut FxHashMap<ContainerID, ContainerIdx>,
673+
root_c_idx: &mut Vec<ContainerIdx>,
669674
) -> Option<NonZeroU16> {
670675
let mut d = depth[target.to_index() as usize];
671676
if d.is_some() {
@@ -680,6 +685,8 @@ fn get_depth(
680685
None
681686
} else if let Some(parent_resolver) = parent_resolver.as_ref() {
682687
let parent_id = parent_resolver(id.clone())?;
688+
let parent_is_root = parent_id.is_root();
689+
let mut parent_was_new = false;
683690
// If the parent is not registered yet, register it lazily instead of unwrapping.
684691
let parent_idx = if let Some(idx) = id_to_idx.get(&parent_id).copied() {
685692
idx
@@ -690,13 +697,27 @@ fn get_depth(
690697
ContainerIdx::from_index_and_type(new_index as u32, parent_id.container_type());
691698
id_to_idx.insert(parent_id.clone(), new_idx);
692699
// Keep depth vector in sync with containers list.
693-
if parent_id.is_root() {
700+
if parent_is_root {
694701
depth.push(NonZeroU16::new(1));
695702
} else {
696703
depth.push(None);
697704
}
705+
parent_was_new = true;
698706
new_idx
699707
};
708+
709+
if parent_is_root {
710+
if parent_was_new {
711+
parents.insert(parent_idx, None);
712+
root_c_idx.push(parent_idx);
713+
} else {
714+
parents.entry(parent_idx).or_insert(None);
715+
}
716+
if depth[parent_idx.to_index() as usize].is_none() {
717+
depth[parent_idx.to_index() as usize] = NonZeroU16::new(1);
718+
}
719+
}
720+
700721
Some(parent_idx)
701722
} else {
702723
return None;
@@ -706,7 +727,17 @@ fn get_depth(
706727
match parent {
707728
Some(p) => {
708729
d = NonZeroU16::new(
709-
get_depth(p, depth, parents, parent_resolver, idx_to_id, id_to_idx)?.get() + 1,
730+
get_depth(
731+
p,
732+
depth,
733+
parents,
734+
parent_resolver,
735+
idx_to_id,
736+
id_to_idx,
737+
root_c_idx,
738+
)?
739+
.get()
740+
+ 1,
710741
);
711742
depth[target.to_index() as usize] = d;
712743
}

crates/loro-wasm/VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.7.1
1+
1.8.1

crates/loro/tests/issue.rs

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![allow(unexpected_cfgs)]
2-
use loro::{LoroDoc, UndoManager};
2+
use loro::{EncodedBlobMode, ExportMode, LoroDoc, UndoManager};
33
use std::sync::{Arc, Mutex};
4+
use tracing::{trace, trace_span};
45

56
#[ctor::ctor]
67
fn init() {
@@ -251,3 +252,55 @@ fn undo_tree_mov_between_children() {
251252
let doc_value_1 = doc.get_deep_value();
252253
assert_eq!(doc_value_0, doc_value_1);
253254
}
255+
256+
#[test]
257+
fn issue_822_tree_shallow_snapshot_roundtrip() {
258+
let snapshot_bytes = include_bytes!("./issue_822.bin");
259+
let doc = LoroDoc::new();
260+
doc.import(snapshot_bytes).expect("import snapshot blob");
261+
262+
let tree = doc.get_tree("nodes");
263+
let tree_before = tree.get_value();
264+
let doc_before = doc.get_value();
265+
266+
let snapshot_meta =
267+
LoroDoc::decode_import_blob_meta(snapshot_bytes, false).expect("decode snapshot meta");
268+
assert!(snapshot_meta.mode.is_snapshot());
269+
let imported_is_shallow = snapshot_meta.mode == EncodedBlobMode::ShallowSnapshot;
270+
271+
let frontiers = doc.state_frontiers();
272+
let shallow_bytes = trace_span!("EXPORT").in_scope(|| {
273+
doc.export(ExportMode::shallow_snapshot(&frontiers))
274+
.expect("export shallow snapshot")
275+
});
276+
277+
let snapshot_meta_1 = LoroDoc::decode_import_blob_meta(&shallow_bytes, false).unwrap();
278+
assert!(matches!(
279+
snapshot_meta_1.mode,
280+
EncodedBlobMode::ShallowSnapshot
281+
));
282+
283+
let shallow_meta =
284+
LoroDoc::decode_import_blob_meta(&shallow_bytes, false).expect("decode shallow meta");
285+
assert_eq!(shallow_meta.mode, EncodedBlobMode::ShallowSnapshot);
286+
287+
let shallow_doc = LoroDoc::new();
288+
trace_span!("FINAL_IMPORT").in_scope(|| {
289+
trace!("bytes.len: {}", shallow_bytes.len());
290+
shallow_doc
291+
.import(&shallow_bytes)
292+
.expect("import shallow snapshot");
293+
});
294+
295+
assert!(shallow_doc.is_shallow());
296+
assert_eq!(doc.is_shallow(), imported_is_shallow);
297+
298+
let tree_after = shallow_doc.get_tree("nodes").get_value();
299+
let doc_after = shallow_doc.get_value();
300+
301+
assert_eq!(
302+
tree_before, tree_after,
303+
"tree shallow value should roundtrip"
304+
);
305+
assert_eq!(doc_before, doc_after, "doc shallow value should roundtrip");
306+
}

crates/loro/tests/issue_822.bin

7.51 KB
Binary file not shown.

0 commit comments

Comments
 (0)