Skip to content

Commit

Permalink
Merge branch 'dev' into feat-edit-on-detached-mode
Browse files Browse the repository at this point in the history
  • Loading branch information
zxch3n committed Sep 24, 2024
2 parents 3651ce0 + 88e9ec9 commit c485d64
Show file tree
Hide file tree
Showing 6 changed files with 237 additions and 11 deletions.
116 changes: 116 additions & 0 deletions crates/fuzz/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11126,6 +11126,122 @@ fn detached_editing_failed_case_0() {
],
)
}
fn gc_fuzz_23() {
test_multi_sites_with_gc(
5,
vec![FuzzTarget::All],
&mut [
Handle {
site: 7,
target: 7,
container: 7,
action: Generic(GenericAction {
value: I32(117901063),
bool: true,
key: 117901063,
pos: 506381209866536711,
length: 506382279195428615,
prop: 506381209866536711,
}),
},
Handle {
site: 7,
target: 7,
container: 7,
action: Generic(GenericAction {
value: Container(MovableList),
bool: true,
key: 117901063,
pos: 13527604947524716295,
length: 13527612320720337851,
prop: 13527612320720337851,
}),
},
Sync { from: 187, to: 187 },
Sync { from: 181, to: 181 },
Handle {
site: 7,
target: 7,
container: 7,
action: Generic(GenericAction {
value: I32(117901063),
bool: true,
key: 117964799,
pos: 506381209866536711,
length: 506381209866536711,
prop: 18446744073709549575,
}),
},
Handle {
site: 7,
target: 7,
container: 7,
action: Generic(GenericAction {
value: Container(Counter),
bool: true,
key: 3149642683,
pos: 13527612320720337851,
length: 13527612320720337851,
prop: 13093571283691877666,
}),
},
Sync { from: 181, to: 181 },
Sync { from: 181, to: 181 },
Handle {
site: 41,
target: 41,
container: 41,
action: Generic(GenericAction {
value: I32(-1929117696),
bool: true,
key: 691087657,
pos: 6997314489568995625,
length: 6971295109,
prop: 11511094678367764480,
}),
},
Handle {
site: 41,
target: 41,
container: 45,
action: Generic(GenericAction {
value: I32(73078),
bool: true,
key: 1985478573,
pos: 506381209866567542,
length: 18446744069532485383,
prop: 18446527469918879743,
}),
},
Handle {
site: 0,
target: 0,
container: 255,
action: Generic(GenericAction {
value: I32(-65536),
bool: true,
key: 125698048,
pos: 18391573895942504331,
length: 18446528569430507519,
prop: 2675172263518535679,
}),
},
Handle {
site: 0,
target: 0,
container: 0,
action: Generic(GenericAction {
value: I32(0),
bool: false,
key: 0,
pos: 0,
length: 0,
prop: 0,
}),
},
],
)
}

#[test]
fn minify() {
Expand Down
2 changes: 1 addition & 1 deletion crates/loro-internal/src/encoding/fast_snapshot.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ pub(crate) fn encode_snapshot_at<W: std::io::Write>(
}
doc.checkout_without_emitting(&version_before_start)
.unwrap();
doc.ignore_events();
doc.drop_pending_events();
Ok(())
}

Expand Down
33 changes: 27 additions & 6 deletions crates/loro-internal/src/encoding/gc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,18 +55,21 @@ pub(crate) fn export_gc_snapshot<W: std::io::Write>(
&start_vv, &start_from,
);

let latest_frontiers = oplog.frontiers().clone();
let state_frontiers = doc.state_frontiers();
let is_attached = !doc.is_detached();
let oplog_bytes = oplog.export_change_store_from(&start_vv, &start_from);
let latest_vv = oplog.vv();
let ops_num: usize = latest_vv.sub_iter(&start_vv).map(|x| x.atom_len()).sum();
drop(oplog);
doc.checkout(&start_from)?;
doc.checkout_without_emitting(&start_from)?;
let mut state = doc.app_state().lock().unwrap();
let alive_containers = state.ensure_all_alive_containers();
let alive_c_bytes: BTreeSet<Vec<u8>> = alive_containers.iter().map(|x| x.to_bytes()).collect();
state.store.flush();
let gc_state_kv = state.store.get_kv().clone();
drop(state);
doc.checkout_to_latest();
doc.checkout_without_emitting(&latest_frontiers).unwrap();
let state_bytes = if ops_num > MAX_OPS_NUM_TO_ENCODE_WITHOUT_LATEST_STATE {
let mut state = doc.app_state().lock().unwrap();
state.ensure_all_alive_containers();
Expand All @@ -90,6 +93,15 @@ pub(crate) fn export_gc_snapshot<W: std::io::Write>(
};

_encode_snapshot(snapshot, w);
if state_frontiers != latest_frontiers {
doc.checkout_without_emitting(&state_frontiers).unwrap();
}

if is_attached {
doc.set_detached(false);
}

doc.drop_pending_events();
Ok(start_from)
}

Expand All @@ -100,7 +112,6 @@ pub(crate) fn export_state_only_snapshot<W: std::io::Write>(
) -> LoroResult<Frontiers> {
let oplog = doc.oplog().lock().unwrap();
let start_from = calc_gc_doc_start(&oplog, start_from);
trace!("gc_start_from {:?}", &start_from);
let mut start_vv = oplog.dag().frontiers_to_vv(&start_from).unwrap();
for id in start_from.iter() {
// we need to include the ops in start_from, this can make things easier
Expand All @@ -119,15 +130,16 @@ pub(crate) fn export_state_only_snapshot<W: std::io::Write>(

let oplog_bytes =
oplog.export_change_store_in_range(&start_vv, &start_from, &to_vv, &start_from);
let state_frontiers = doc.state_frontiers();
let is_attached = !doc.is_detached();
drop(oplog);
doc.checkout(&start_from)?;
doc.checkout_without_emitting(&start_from)?;
let mut state = doc.app_state().lock().unwrap();
let alive_containers = state.ensure_all_alive_containers();
let alive_c_bytes: BTreeSet<Vec<u8>> = alive_containers.iter().map(|x| x.to_bytes()).collect();
state.store.flush();
let gc_state_kv = state.store.get_kv().clone();
drop(state);
doc.checkout_to_latest();
let state_bytes = None;
gc_state_kv.retain_keys(&alive_c_bytes);
gc_state_kv.insert(FRONTIERS_KEY, start_from.encode().into());
Expand All @@ -137,8 +149,17 @@ pub(crate) fn export_state_only_snapshot<W: std::io::Write>(
state_bytes,
gc_bytes: gc_state_bytes,
};

_encode_snapshot(snapshot, w);

if state_frontiers != start_from {
doc.checkout_without_emitting(&state_frontiers).unwrap();
}

if is_attached {
doc.set_detached(false);
}

doc.drop_pending_events();
Ok(start_from)
}

Expand Down
17 changes: 15 additions & 2 deletions crates/loro-internal/src/loro.rs
Original file line number Diff line number Diff line change
Expand Up @@ -546,7 +546,7 @@ impl LoroDoc {
}
}

pub(crate) fn ignore_events(&self) {
pub(crate) fn drop_pending_events(&self) {
let _events = {
let mut state = self.state.lock().unwrap();
state.take_events()
Expand Down Expand Up @@ -1064,6 +1064,11 @@ impl LoroDoc {
self.oplog_vv()
);

if &from_frontiers == frontiers {
self.renew_txn_if_auto_commit();
return Ok(());
}

let oplog = self.oplog.lock().unwrap();
if oplog.dag.is_on_trimmed_history(frontiers) {
drop(oplog);
Expand Down Expand Up @@ -1276,6 +1281,10 @@ impl LoroDoc {
.ok_or(CannotFindRelativePosition::ContainerDeleted)?;
// We know where the target id is when we trace back to the delete_op_id.
let Some(delete_op_id) = find_last_delete_op(&oplog, id, idx) else {
if oplog.trimmed_vv().includes_id(id) {
return Err(CannotFindRelativePosition::HistoryCleared);
}

tracing::error!("Cannot find id {}", id);
return Err(CannotFindRelativePosition::IdNotFound);
};
Expand Down Expand Up @@ -1473,8 +1482,12 @@ impl LoroDoc {
}
}

// FIXME: PERF: This method is quite slow because it iterates all the changes
fn find_last_delete_op(oplog: &OpLog, id: ID, idx: ContainerIdx) -> Option<ID> {
let start_vv = oplog.dag.frontiers_to_vv(&id.into())?;
let start_vv = oplog
.dag
.frontiers_to_vv(&id.into())
.unwrap_or_else(|| oplog.trimmed_vv().to_vv());
for change in oplog.iter_changes_causally_rev(&start_vv, oplog.vv()) {
for op in change.ops.iter().rev() {
if op.container != idx {
Expand Down
8 changes: 7 additions & 1 deletion crates/loro-internal/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1162,11 +1162,17 @@ impl DocState {
// the metadata of this node. When the user get the deep value,
// we need to add a field named `meta` to the tree node,
// whose value is deep value of map container.
for node in list.iter() {
let mut list = Arc::unwrap_or_clone(list);
while let Some(node) = list.pop() {
let map = node.as_map().unwrap();
let meta = map.get("meta").unwrap();
let id = meta.as_container().unwrap();
ans.push(id.clone());
let children = map.get("children").unwrap();
let children = children.as_list().unwrap();
for child in children.iter() {
list.push(child.clone());
}
}
} else {
for item in list.iter() {
Expand Down
72 changes: 71 additions & 1 deletion crates/loro/tests/integration_test/gc_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
};

use super::gen_action;
use loro::{ExportMode, Frontiers, LoroDoc, ID};
use loro::{cursor::CannotFindRelativePosition, ExportMode, Frontiers, LoroDoc, ID};

#[test]
fn test_gc() -> anyhow::Result<()> {
Expand Down Expand Up @@ -240,3 +240,73 @@ fn the_vv_on_gc_doc() -> anyhow::Result<()> {

Ok(())
}

#[test]
fn no_event_when_exporting_gc_snapshot() -> anyhow::Result<()> {
let doc = LoroDoc::new();
doc.set_peer_id(1)?;
gen_action(&doc, 0, 10);
doc.commit();
let _id = doc.subscribe_root(Arc::new(|_diff| {
panic!("should not emit event");
}));
let _snapshot = doc.export(loro::ExportMode::gc_snapshot_from_id(ID::new(1, 3)));
Ok(())
}

#[test]
fn test_cursor_that_cannot_be_found_when_exporting_gc_snapshot() -> anyhow::Result<()> {
let doc = LoroDoc::new();
doc.set_peer_id(1)?;
doc.get_text("text").insert(0, "Hello world")?;
let c = doc
.get_text("text")
.get_cursor(3, loro::cursor::Side::Left)
.unwrap();
doc.get_text("text").delete(0, 5)?;
doc.commit();
let snapshot = doc.export(loro::ExportMode::gc_snapshot(&doc.oplog_frontiers()));
let new_doc = LoroDoc::new();
new_doc.import(&snapshot)?;
let result = new_doc.get_cursor_pos(&c);
match result {
Ok(v) => {
dbg!(v);
unreachable!()
}
Err(CannotFindRelativePosition::HistoryCleared) => {}
Err(x) => {
dbg!(x);
unreachable!()
}
}
Ok(())
}

#[test]
fn test_cursor_that_can_be_found_when_exporting_gc_snapshot() -> anyhow::Result<()> {
let doc = LoroDoc::new();
doc.set_peer_id(1)?;
doc.get_text("text").insert(0, "Hello world")?;
doc.commit();
let c = doc
.get_text("text")
.get_cursor(3, loro::cursor::Side::Left)
.unwrap();
doc.get_text("text").delete(0, 5)?;
doc.commit();
let snapshot = doc.export(loro::ExportMode::gc_snapshot_from_id(ID::new(1, 10)));
let new_doc = LoroDoc::new();
new_doc.import(&snapshot)?;
let result = new_doc.get_cursor_pos(&c);
match result {
Ok(v) => {
assert_eq!(v.current.pos, 0);
}
Err(x) => {
dbg!(x);
unreachable!()
}
}
Ok(())
}

0 comments on commit c485d64

Please sign in to comment.