Skip to content

Commit

Permalink
fix: return err if snapshot container has unknown container (#488)
Browse files Browse the repository at this point in the history
  • Loading branch information
zxch3n authored Oct 2, 2024
1 parent 09a004e commit de93d34
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 106 deletions.
3 changes: 3 additions & 0 deletions crates/loro-common/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,12 +110,15 @@ pub enum LoroTreeError {
TreeNodeDeletedOrNotExist(TreeID),
}

#[non_exhaustive]
#[derive(Error, Debug, PartialEq)]
pub enum LoroEncodeError {
#[error("The frontiers are not found in this doc: {0}")]
FrontiersNotFound(String),
#[error("Trimmed snapshot incompatible with old snapshot format. Use new snapshot format or avoid trimmed snapshots for storage.")]
TrimmedSnapshotIncompatibleWithOldFormat,
#[error("Cannot export trimmed snapshot with unknown container type. Please upgrade the Loro version.")]
UnknownContainer,
}

#[cfg(feature = "wasm")]
Expand Down
78 changes: 42 additions & 36 deletions crates/loro-internal/src/encoding.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
pub(crate) mod arena;
mod encode_reordered;
mod fast_snapshot;
pub(crate) mod json_schema;
mod outdated_encode_reordered;
mod trimmed_snapshot;
pub(crate) mod value;
pub(crate) mod value_register;
use std::borrow::Cow;

pub(crate) use encode_reordered::{
pub(crate) use outdated_encode_reordered::{
decode_op, encode_op, get_op_prop, EncodedDeleteStartId, IterableEncodedDeleteStartId,
};
pub(crate) mod json_schema;
mod outdated_fast_snapshot;
mod trimmed_snapshot;
pub(crate) use value::OwnedValue;

use crate::op::OpWithId;
use crate::version::Frontiers;
Expand All @@ -19,7 +18,7 @@ use loro_common::{IdLpSpan, IdSpan, LoroEncodeError, LoroResult, PeerID, ID};
use num_traits::{FromPrimitive, ToPrimitive};
use rle::{HasLength, Sliceable};
use serde::{Deserialize, Serialize};
pub(crate) use value::OwnedValue;
use std::borrow::Cow;

#[non_exhaustive]
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -234,7 +233,7 @@ pub(crate) fn encode_oplog(oplog: &OpLog, vv: &VersionVector, mode: EncodeMode)
};

let body = match &mode {
EncodeMode::OutdatedRle => encode_reordered::encode_updates(oplog, vv),
EncodeMode::OutdatedRle => outdated_encode_reordered::encode_updates(oplog, vv),
_ => unreachable!(),
};

Expand All @@ -248,12 +247,10 @@ pub(crate) fn decode_oplog(
let ParsedHeaderAndBody { mode, body, .. } = parsed;
match mode {
EncodeMode::OutdatedRle | EncodeMode::OutdatedSnapshot => {
encode_reordered::decode_updates(oplog, body)
}
EncodeMode::FastSnapshot => outdated_fast_snapshot::decode_oplog(oplog, body),
EncodeMode::FastUpdates => {
outdated_fast_snapshot::decode_updates(oplog, body.to_vec().into())
outdated_encode_reordered::decode_updates(oplog, body)
}
EncodeMode::FastSnapshot => fast_snapshot::decode_oplog(oplog, body),
EncodeMode::FastUpdates => fast_snapshot::decode_updates(oplog, body.to_vec().into()),
EncodeMode::Auto => unreachable!(),
}
}
Expand Down Expand Up @@ -331,7 +328,7 @@ fn encode_header_and_body(mode: EncodeMode, body: Vec<u8>) -> Vec<u8> {
}

pub(crate) fn export_snapshot(doc: &LoroDoc) -> Vec<u8> {
let body = encode_reordered::encode_snapshot(
let body = outdated_encode_reordered::encode_snapshot(
&doc.oplog().try_lock().unwrap(),
&mut doc.app_state().try_lock().unwrap(),
&Default::default(),
Expand All @@ -342,40 +339,47 @@ pub(crate) fn export_snapshot(doc: &LoroDoc) -> Vec<u8> {

pub(crate) fn export_fast_snapshot(doc: &LoroDoc) -> Vec<u8> {
encode_with(EncodeMode::FastSnapshot, &mut |ans| {
outdated_fast_snapshot::encode_snapshot(doc, ans);
fast_snapshot::encode_snapshot(doc, ans);
Ok(())
})
.unwrap()
}

pub(crate) fn export_fast_snapshot_at(
pub(crate) fn export_snapshot_at(
doc: &LoroDoc,
frontiers: &Frontiers,
) -> Result<Vec<u8>, LoroEncodeError> {
check_target_version_reachable(doc, frontiers)?;
Ok(encode_with(EncodeMode::FastSnapshot, &mut |ans| {
outdated_fast_snapshot::encode_snapshot_at(doc, frontiers, ans).unwrap();
}))
encode_with(EncodeMode::FastSnapshot, &mut |ans| {
trimmed_snapshot::encode_snapshot_at(doc, frontiers, ans)
})
}

pub(crate) fn export_fast_updates(doc: &LoroDoc, vv: &VersionVector) -> Vec<u8> {
encode_with(EncodeMode::FastUpdates, &mut |ans| {
outdated_fast_snapshot::encode_updates(doc, vv, ans);
fast_snapshot::encode_updates(doc, vv, ans);
Ok(())
})
.unwrap()
}

pub(crate) fn export_fast_updates_in_range(oplog: &OpLog, spans: &[IdSpan]) -> Vec<u8> {
encode_with(EncodeMode::FastUpdates, &mut |ans| {
outdated_fast_snapshot::encode_updates_in_range(oplog, spans, ans);
fast_snapshot::encode_updates_in_range(oplog, spans, ans);
Ok(())
})
.unwrap()
}

pub(crate) fn export_trimmed_snapshot(
doc: &LoroDoc,
f: &Frontiers,
) -> Result<Vec<u8>, LoroEncodeError> {
check_target_version_reachable(doc, f)?;
Ok(encode_with(EncodeMode::FastSnapshot, &mut |ans| {
trimmed_snapshot::export_trimmed_snapshot(doc, f, ans).unwrap();
}))
encode_with(EncodeMode::FastSnapshot, &mut |ans| {
trimmed_snapshot::export_trimmed_snapshot(doc, f, ans)?;
Ok(())
})
}

fn check_target_version_reachable(doc: &LoroDoc, f: &Frontiers) -> Result<(), LoroEncodeError> {
Expand All @@ -392,12 +396,16 @@ pub(crate) fn export_state_only_snapshot(
f: &Frontiers,
) -> Result<Vec<u8>, LoroEncodeError> {
check_target_version_reachable(doc, f)?;
Ok(encode_with(EncodeMode::FastSnapshot, &mut |ans| {
trimmed_snapshot::export_state_only_snapshot(doc, f, ans).unwrap();
}))
encode_with(EncodeMode::FastSnapshot, &mut |ans| {
trimmed_snapshot::export_state_only_snapshot(doc, f, ans)?;
Ok(())
})
}

fn encode_with(mode: EncodeMode, f: &mut dyn FnMut(&mut Vec<u8>)) -> Vec<u8> {
fn encode_with(
mode: EncodeMode,
f: &mut dyn FnMut(&mut Vec<u8>) -> Result<(), LoroEncodeError>,
) -> Result<Vec<u8>, LoroEncodeError> {
// HEADER
let mut ans = Vec::with_capacity(MIN_HEADER_SIZE);
ans.extend(MAGIC_BYTES);
Expand All @@ -406,13 +414,13 @@ fn encode_with(mode: EncodeMode, f: &mut dyn FnMut(&mut Vec<u8>)) -> Vec<u8> {
ans.extend(mode.to_bytes());

// BODY
f(&mut ans);
f(&mut ans)?;

// CHECKSUM in HEADER
let checksum_body = &ans[20..];
let checksum = xxhash_rust::xxh32::xxh32(checksum_body, XXH_SEED);
ans[16..20].copy_from_slice(&checksum.to_le_bytes());
ans
Ok(ans)
}

pub(crate) fn decode_snapshot(
Expand All @@ -421,10 +429,8 @@ pub(crate) fn decode_snapshot(
body: &[u8],
) -> Result<(), LoroError> {
match mode {
EncodeMode::OutdatedSnapshot => encode_reordered::decode_snapshot(doc, body),
EncodeMode::FastSnapshot => {
outdated_fast_snapshot::decode_snapshot(doc, body.to_vec().into())
}
EncodeMode::OutdatedSnapshot => outdated_encode_reordered::decode_snapshot(doc, body),
EncodeMode::FastSnapshot => fast_snapshot::decode_snapshot(doc, body.to_vec().into()),
_ => unreachable!(),
}
}
Expand Down Expand Up @@ -453,7 +459,7 @@ pub struct ImportBlobMetadata {
impl LoroDoc {
/// Decodes the metadata for an imported blob from the provided bytes.
pub fn decode_import_blob_meta(blob: &[u8]) -> LoroResult<ImportBlobMetadata> {
encode_reordered::decode_import_blob_meta(blob)
outdated_encode_reordered::decode_import_blob_meta(blob)
}
}

Expand Down
2 changes: 1 addition & 1 deletion crates/loro-internal/src/encoding/arena.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
use serde_columnar::{columnar, ColumnarError};

use super::{
encode_reordered::{PeerIdx, MAX_DECODED_SIZE},
outdated_encode_reordered::{PeerIdx, MAX_DECODED_SIZE},
value::{Value, ValueDecodedArenasTrait, ValueEncodeRegister},
value_register::ValueRegister,
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@
//!
use std::io::{Read, Write};

use crate::{
encoding::trimmed_snapshot, oplog::ChangeStore, version::Frontiers, LoroDoc, OpLog,
VersionVector,
};
use crate::{encoding::trimmed_snapshot, oplog::ChangeStore, LoroDoc, OpLog, VersionVector};
use bytes::{Buf, Bytes};
use loro_common::{IdSpan, LoroEncodeError, LoroError, LoroResult};
use loro_common::{IdSpan, LoroError, LoroResult};
use tracing::trace;

use super::encode_reordered::{import_changes_to_oplog, ImportChangesResult};
use super::outdated_encode_reordered::{import_changes_to_oplog, ImportChangesResult};

pub(crate) const EMPTY_MARK: &[u8] = b"E";
pub(super) struct Snapshot {
Expand Down Expand Up @@ -211,53 +208,6 @@ pub(crate) fn encode_snapshot<W: std::io::Write>(doc: &LoroDoc, w: &mut W) {
}
}

pub(crate) fn encode_snapshot_at<W: std::io::Write>(
doc: &LoroDoc,
frontiers: &Frontiers,
w: &mut W,
) -> Result<(), LoroEncodeError> {
let version_before_start = doc.oplog_frontiers();
doc.checkout_without_emitting(frontiers).unwrap();
{
let mut state = doc.app_state().try_lock().unwrap();
let oplog = doc.oplog().try_lock().unwrap();
let is_gc = state.store.trimmed_store().is_some();
if is_gc {
unimplemented!()
}

assert!(!state.is_in_txn());
let Some(oplog_bytes) = oplog.fork_changes_up_to(frontiers) else {
return Err(LoroEncodeError::FrontiersNotFound(format!(
"frontiers: {:?} when export in SnapshotAt mode",
frontiers
)));
};

if oplog.is_trimmed() {
assert_eq!(
oplog.trimmed_frontiers(),
state.store.trimmed_frontiers().unwrap()
);
}

state.ensure_all_alive_containers();
let state_bytes = state.store.encode();
_encode_snapshot(
Snapshot {
oplog_bytes,
state_bytes: Some(state_bytes),
trimmed_bytes: Bytes::new(),
},
w,
);
}
doc.checkout_without_emitting(&version_before_start)
.unwrap();
doc.drop_pending_events();
Ok(())
}

pub(crate) fn decode_oplog(oplog: &mut OpLog, bytes: &[u8]) -> Result<(), LoroError> {
let oplog_len = u32::from_le_bytes(bytes[0..4].try_into().unwrap());
let oplog_bytes = &bytes[4..4 + oplog_len as usize];
Expand Down
4 changes: 3 additions & 1 deletion crates/loro-internal/src/encoding/json_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ use crate::{
OpLog, VersionVector,
};

use super::encode_reordered::{import_changes_to_oplog, ImportChangesResult, ValueRegister};
use super::outdated_encode_reordered::{
import_changes_to_oplog, ImportChangesResult, ValueRegister,
};
use json::{JsonOpContent, JsonSchema};

const SCHEMA_VERSION: u8 = 1;
Expand Down
Loading

0 comments on commit de93d34

Please sign in to comment.