Repository navigation
Tree import can miss the Create event for a child of a node revived from a deleted subtree #1157
Description
Importing an update that revives a tree node from a deleted subtree can leave out the Create event for one of the node's children. The document state is correct; only the events are wrong, so an event-driven mirror loses the child.
Reproduces on main (1eb14b3) and on #1152 / #1153; found by the adversarial review of those PRs (their random checkout harness, seed 138, "partial" mode).
Shape
Node 0@1008 is created under a parent that another peer has already deleted, gets a child 3@1008, and is then moved to the root. A document that has the earlier part of the history imports the rest in one import. The events contain Create for 0@1008 but none for 3@1008, which is alive under 0@1008 afterwards. Simple two- or three-peer versions of this shape do emit the child's Create; this one needs the extra concurrency in the recorded history, so the repro uses the two update blobs.
Repro (loro crate)
use base64::Engine;
use loro::{event::Diff, ContainerTrait, LoroDoc};
use std::sync::{Arc, Mutex};
const BASE: &str = "bG9ybwAAAAAAAAAAAAAAAACHcG0ABEEAAQABARABAgAAAAAAAAABAQAAAAAABQEAAAEABgEEAQAAAggBawVidXJzdAAOAQQCAQACAQACAQsCAQEAAwPRB0wAAQEBARsC6QMAAAAAAAACAAAAAAAAAAEBAQEBAQAAAAAFAQAAAQAGAQQBAAACCAFrBWJ1cnN0AA4BBAIBAAIBAAIBCwIBAQADA9oG";
const REST: &str = "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";
#[test]
fn imported_revival_emits_create_for_every_child() {
let b64 = base64::engine::general_purpose::STANDARD;
let doc = LoroDoc::new();
doc.import(&b64.decode(BASE).unwrap()).unwrap();
let tree = doc.get_tree("tree");
let before: Vec<_> = tree.get_nodes(false).into_iter().map(|n| n.id).collect();
let created = Arc::new(Mutex::new(Vec::new()));
let c2 = created.clone();
let tid = tree.id();
let _sub = doc.subscribe_root(Arc::new(move |e| {
for c in e.events.iter() {
if c.target == &tid {
if let Diff::Tree(t) = &c.diff {
for i in t.diff.iter() {
if matches!(i.action, loro::TreeExternalDiff::Create { .. }) {
c2.lock().unwrap().push(i.target);
}
}
}
}
}
}));
doc.import(&b64.decode(REST).unwrap()).unwrap();
for n in tree.get_nodes(false) {
if !before.contains(&n.id) {
assert!(
created.lock().unwrap().contains(&n.id),
"node {} (parent {:?}) got no Create event",
n.id,
n.parent
);
}
}
}Output: node 3@1008 (parent Node(0@1008)) got no Create event. The events emitted by the second import are:
0@1003 Create parent Root index 0
2@1001 Create parent Root index 1
1@1017 Create parent 0@1003 index 0
2@1001 Delete old_parent Root old_index 1
0@1010 Create parent Root index 1
3@1010 Create parent Root index 2
0@1008 Create parent Root index 1
0@1012 Create parent 3@1010 index 0
0@1010 Delete old_parent Root old_index 2
Notes
- A fresh document importing both blobs gets the same events, so it does not depend on an earlier checkout.
TreeState::apply_diff_and_convertadds aCreatefor each existing child when a node is revived; the child here is created in the same batch while its parent is still in the deleted subtree, which is probably where it gets lost (the diff item for the child is applied as a move inside a deleted subtree and never emitted, and the revival of the parent happens before the child is attached, or the other way around).
🤖 Generated with Claude Code
Activity
fix: emit tree events for revived subtrees on import (#1157)
fix: issue sweep (#1118, #1162, #1155, #1165, #1156, #1164, #1157, #1154