1use crate::encoding::json_schema::{encode_change, export_json_in_id_span};
2pub use crate::encoding::ExportMode;
3use crate::pre_commit::{FirstCommitFromPeerCallback, FirstCommitFromPeerPayload};
4pub use crate::state::analyzer::{ContainerAnalysisInfo, DocAnalysis};
5use crate::sync::{AtomicBool, AtomicUsize};
6pub(crate) use crate::LoroDocInner;
7use crate::{
8 arena::SharedArena,
9 change::{Change, Timestamp},
10 configure::{Configure, DefaultRandom, SecureRandomGenerator, StyleConfig},
11 container::{
12 idx::ContainerIdx, list::list_op::InnerListOp, richtext::config::StyleConfigMap,
13 IntoContainerId,
14 },
15 cursor::{AbsolutePosition, CannotFindRelativePosition, Cursor, PosQueryResult},
16 dag::{Dag, DagUtils},
17 diff_calc::{DiffCalculator, DiffMode},
18 encoding::{
19 self, decode_snapshot, export_fast_snapshot, export_fast_updates,
20 export_fast_updates_in_range, export_shallow_snapshot, export_snapshot_at,
21 export_state_only_snapshot,
22 json_schema::{encode_change_to_json, json::JsonSchema},
23 parse_header_and_body, EncodeMode, ImportBlobMetadata, ImportStatus, ParsedHeaderAndBody,
24 },
25 event::{str_to_path, EventTriggerKind, Index, InternalDocDiff},
26 handler::{Handler, MovableListHandler, TextHandler, TreeHandler, ValueOrHandler},
27 id::PeerID,
28 json::JsonChange,
29 op::InnerContent,
30 oplog::{loro_dag::FrontiersNotIncluded, OpLog},
31 state::DocState,
32 subscription::{LocalUpdateCallback, Observer, Subscriber},
33 undo::DiffBatch,
34 utils::subscription::{SubscriberSetWithQueue, Subscription},
35 version::{shrink_frontiers, Frontiers, ImVersionVector, VersionRange, VersionVectorDiff},
36 ChangeMeta, DocDiff, HandlerTrait, InternalString, ListHandler, LoroDoc, LoroError, MapHandler,
37 VersionVector,
38};
39use crate::{change::ChangeRef, lock::LockKind};
40use crate::{lock::LoroMutexGuard, pre_commit::PreCommitCallback};
41use crate::{
42 lock::{LoroLockGroup, LoroMutex},
43 txn::Transaction,
44};
45use either::Either;
46use loro_common::{
47 ContainerID, ContainerType, HasCounterSpan, HasIdSpan, HasLamportSpan, IdSpan, LoroEncodeError,
48 LoroResult, LoroValue, ID,
49};
50use rle::HasLength;
51use rustc_hash::{FxHashMap, FxHashSet};
52use std::{
53 borrow::Cow,
54 cmp::Ordering,
55 collections::{hash_map::Entry, BinaryHeap},
56 ops::ControlFlow,
57 sync::{
58 atomic::Ordering::{Acquire, Release},
59 Arc,
60 },
61};
62use tracing::{debug_span, info_span, instrument, warn};
63
64impl Default for LoroDoc {
65 fn default() -> Self {
66 Self::new()
67 }
68}
69
70#[cfg(test)]
71thread_local! {
72 static PANIC_AT_BATCH_IMPORT_BLOB: std::cell::Cell<Option<usize>> = const { std::cell::Cell::new(None) };
75}
76
77#[cfg(test)]
78pub(crate) fn panic_at_batch_import_blob_for_test(blobs_to_import_first: usize) {
79 PANIC_AT_BATCH_IMPORT_BLOB.with(|f| f.set(Some(blobs_to_import_first)));
80}
81
82#[cfg(test)]
83fn take_panic_at_batch_import_blob_for_test() {
84 PANIC_AT_BATCH_IMPORT_BLOB.with(|f| match f.get() {
85 Some(0) => {
86 f.set(None);
87 panic!("batch import failpoint");
88 }
89 Some(n) => f.set(Some(n - 1)),
90 None => {}
91 });
92}
93
94impl std::fmt::Debug for LoroDocInner {
95 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
96 f.debug_struct("LoroDoc")
97 .field("config", &self.config)
98 .field("auto_commit", &self.auto_commit)
99 .field("detached", &self.detached)
100 .finish()
101 }
102}
103
104#[cfg(test)]
105thread_local! {
106 static ISOLATED_FAST_PATH_HITS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
108}
109
110#[cfg(test)]
111pub(crate) fn isolated_fast_path_hits_for_test() -> usize {
112 ISOLATED_FAST_PATH_HITS.with(|hits| hits.get())
113}
114
115impl LoroDoc {
116 #[inline]
124 pub fn with_barrier<F, R>(&self, f: F) -> R
125 where
126 F: FnOnce() -> R,
127 {
128 let (options, guard) = self.implicit_commit_then_stop();
129 let result = f();
130 drop(guard);
131 self.renew_txn_if_auto_commit(options);
132 result
133 }
134
135 pub fn new() -> Self {
136 let visible_op_count = Arc::new(AtomicUsize::new(0));
137 let oplog = OpLog::new(visible_op_count.clone());
138 let arena = oplog.arena.clone();
139 let config: Configure = oplog.configure.clone();
140 let lock_group = LoroLockGroup::new();
141 let global_txn = Arc::new(lock_group.new_lock(None, LockKind::Txn));
142 let inner = Arc::new_cyclic(|w| {
143 let state = DocState::new_arc(w.clone(), arena.clone(), config.clone(), &lock_group);
144 LoroDocInner {
145 oplog: Arc::new(lock_group.new_lock(oplog, LockKind::OpLog)),
146 state,
147 config,
148 visible_op_count,
149 detached: AtomicBool::new(false),
150 auto_commit: AtomicBool::new(false),
151 observer: Arc::new(Observer::new(arena.clone())),
152 diff_calculator: Arc::new(
153 lock_group.new_lock(DiffCalculator::new(true), LockKind::DiffCalculator),
154 ),
155 txn: global_txn,
156 arena,
157 local_update_subs: SubscriberSetWithQueue::new(),
158 peer_id_change_subs: SubscriberSetWithQueue::new(),
159 pre_commit_subs: SubscriberSetWithQueue::new(),
160 first_commit_from_peer_subs: SubscriberSetWithQueue::new(),
161 }
162 });
163 LoroDoc { inner }
164 }
165
166 pub fn fork(&self) -> Self {
167 if self.is_detached() {
168 return self
169 .fork_at(&self.state_frontiers())
170 .expect("fork_at on detached doc should not fail");
171 }
172
173 let snapshot = self
174 .with_barrier(|| encoding::fast_snapshot::encode_snapshot_inner(self))
175 .expect("forking a valid document should encode a snapshot");
176 let doc = Self::new();
177 doc.with_barrier(|| {
178 encoding::fast_snapshot::decode_snapshot_inner(snapshot, &doc, Default::default())
179 })
180 .unwrap();
181 doc.set_config(&self.config);
182 if self.auto_commit.load(std::sync::atomic::Ordering::Relaxed) {
183 doc.start_auto_commit();
184 }
185 doc
186 }
187 pub fn set_detached_editing(&self, enable: bool) {
203 self.config.set_detached_editing(enable);
204 if enable && self.is_detached() {
205 self.with_barrier(|| {
206 self.renew_peer_id();
207 });
208 }
209 }
210
211 #[inline]
213 pub fn new_auto_commit() -> Self {
214 let doc = Self::new();
215 doc.start_auto_commit();
216 doc
217 }
218
219 #[inline(always)]
220 pub fn set_peer_id(&self, peer: PeerID) -> LoroResult<()> {
221 if peer == PeerID::MAX {
222 return Err(LoroError::InvalidPeerID);
223 }
224 let next_id = self.oplog.lock().next_id(peer);
225 if self.auto_commit.load(Acquire) {
226 let doc_state = self.state.lock();
227 doc_state
228 .peer
229 .store(peer, std::sync::atomic::Ordering::Relaxed);
230
231 if doc_state.is_in_txn() {
232 drop(doc_state);
233 self.with_barrier(|| {});
235 }
236 self.peer_id_change_subs.emit(&(), next_id);
237 return Ok(());
238 }
239
240 let doc_state = self.state.lock();
241 if doc_state.is_in_txn() {
242 return Err(LoroError::TransactionError(
243 "Cannot change peer id during transaction"
244 .to_string()
245 .into_boxed_str(),
246 ));
247 }
248
249 doc_state
250 .peer
251 .store(peer, std::sync::atomic::Ordering::Relaxed);
252 drop(doc_state);
253 self.peer_id_change_subs.emit(&(), next_id);
254 Ok(())
255 }
256
257 pub(crate) fn renew_peer_id(&self) {
259 let mut peer_id = DefaultRandom.next_u64();
260 while peer_id == PeerID::MAX {
261 peer_id = DefaultRandom.next_u64();
262 }
263 self.set_peer_id(peer_id).unwrap();
264 }
265
266 #[inline]
278 #[must_use]
279 pub fn implicit_commit_then_stop(
280 &self,
281 ) -> (
282 Option<CommitOptions>,
283 LoroMutexGuard<'_, Option<Transaction>>,
284 ) {
285 let (a, b) = self.commit_internal(CommitOptions::new().immediate_renew(false), true);
287 (a, b.unwrap())
288 }
289
290 #[inline]
295 pub fn commit_then_renew(&self) -> Option<CommitOptions> {
296 self.commit_internal(CommitOptions::new().immediate_renew(true), false)
298 .0
299 }
300
301 fn before_commit(&self) -> Option<LoroMutexGuard<'_, Option<Transaction>>> {
306 let mut txn_guard = self.txn.lock();
307 let Some(txn) = txn_guard.as_mut() else {
308 return Some(txn_guard);
309 };
310
311 if txn.is_peer_first_appearance {
312 txn.is_peer_first_appearance = false;
313 drop(txn_guard);
314 self.first_commit_from_peer_subs.emit(
316 &(),
317 FirstCommitFromPeerPayload {
318 peer: self.peer_id(),
319 },
320 );
321 }
322
323 None
324 }
325
326 #[instrument(skip_all)]
334 fn commit_internal(
335 &self,
336 config: CommitOptions,
337 preserve_on_empty: bool,
338 ) -> (
339 Option<CommitOptions>,
340 Option<LoroMutexGuard<'_, Option<Transaction>>>,
341 ) {
342 if !self.auto_commit.load(Acquire) {
343 let txn_guard = self.txn.lock();
344 return (None, Some(txn_guard));
347 }
348
349 loop {
350 if let Some(txn_guard) = self.before_commit() {
351 return (None, Some(txn_guard));
352 }
353
354 let mut txn_guard = self.txn.lock();
355 let txn = txn_guard.take();
356 let Some(mut txn) = txn else {
357 return (None, Some(txn_guard));
358 };
359 let on_commit = txn.take_on_commit();
360 if let Some(origin) = config.origin.clone() {
361 txn.set_origin(origin);
362 }
363
364 if let Some(timestamp) = config.timestamp {
365 txn.set_timestamp(timestamp);
366 }
367
368 if let Some(msg) = config.commit_msg.as_ref() {
369 txn.set_msg(Some(msg.clone()));
370 }
371
372 let id_span = txn.id_span();
373 let mut options = txn.commit().unwrap();
374 if let Some(opts) = options.as_mut() {
376 if config.origin.is_some() {
378 opts.set_origin(None);
379 }
380 if !preserve_on_empty {
382 options = None;
383 }
384 }
385 if config.immediate_renew && self.can_edit() {
386 let mut t = self.txn().unwrap();
387 if let Some(options) = options.as_ref() {
388 t.set_options(options.clone());
389 }
390 *txn_guard = Some(t);
391 }
392
393 if let Some(on_commit) = on_commit {
394 drop(txn_guard);
395 on_commit(&self.state, &self.oplog, id_span);
396 txn_guard = self.txn.lock();
397 if !config.immediate_renew && txn_guard.is_some() {
398 continue;
400 }
401 }
402
403 return (
404 options,
405 if !config.immediate_renew {
406 Some(txn_guard)
407 } else {
408 None
409 },
410 );
411 }
412 }
413
414 #[instrument(skip_all)]
419 pub fn commit_with(
420 &self,
421 config: CommitOptions,
422 ) -> (
423 Option<CommitOptions>,
424 Option<LoroMutexGuard<'_, Option<Transaction>>>,
425 ) {
426 self.commit_internal(config, false)
427 }
428
429 pub fn set_next_commit_message(&self, message: &str) {
431 let mut binding = self.txn.lock();
432 let Some(txn) = binding.as_mut() else {
433 return;
434 };
435
436 if message.is_empty() {
437 txn.set_msg(None)
438 } else {
439 txn.set_msg(Some(message.into()))
440 }
441 }
442
443 pub fn set_next_commit_origin(&self, origin: &str) {
445 let mut txn = self.txn.lock();
446 if let Some(txn) = txn.as_mut() {
447 txn.set_origin(origin.into());
448 }
449 }
450
451 pub fn set_next_commit_timestamp(&self, timestamp: Timestamp) {
453 let mut txn = self.txn.lock();
454 if let Some(txn) = txn.as_mut() {
455 txn.set_timestamp(timestamp);
456 }
457 }
458
459 pub fn set_next_commit_options(&self, options: CommitOptions) {
461 let mut txn = self.txn.lock();
462 if let Some(txn) = txn.as_mut() {
463 txn.set_options(options);
464 }
465 }
466
467 pub fn clear_next_commit_options(&self) {
469 let mut txn = self.txn.lock();
470 if let Some(txn) = txn.as_mut() {
471 txn.set_options(CommitOptions::new());
472 }
473 }
474
475 #[inline]
486 pub fn set_record_timestamp(&self, record: bool) {
487 self.config.set_record_timestamp(record);
488 }
489
490 #[inline]
495 pub fn set_change_merge_interval(&self, interval: i64) {
496 self.config.set_merge_interval(interval);
497 }
498
499 pub fn can_edit(&self) -> bool {
500 !self.is_detached() || self.config.detached_editing()
501 }
502
503 pub fn is_detached_editing_enabled(&self) -> bool {
504 self.config.detached_editing()
505 }
506
507 #[inline]
508 pub fn config_text_style(&self, text_style: StyleConfigMap) {
509 self.config.text_style_config.write().map = text_style.map;
510 }
511
512 #[inline]
513 pub fn config_default_text_style(&self, text_style: Option<StyleConfig>) {
514 self.config.text_style_config.write().default_style = text_style;
515 }
516 pub fn from_snapshot(bytes: &[u8]) -> LoroResult<Self> {
517 let doc = Self::new();
518 let ParsedHeaderAndBody { mode, body, .. } = parse_header_and_body(bytes, true)?;
519 if mode.is_snapshot() {
520 doc.with_barrier(|| -> Result<(), LoroError> {
521 decode_snapshot(&doc, mode, body, Default::default())?;
522 Ok(())
523 })?;
524 Ok(doc)
525 } else {
526 Err(LoroError::DecodeError(
527 "Invalid encode mode".to_string().into(),
528 ))
529 }
530 }
531
532 #[inline(always)]
534 pub fn can_reset_with_snapshot(&self) -> bool {
535 let oplog = self.oplog.lock();
536 if oplog.batch_importing {
537 return false;
538 }
539
540 if self.is_detached() {
541 return false;
542 }
543
544 oplog.is_empty() && self.state.lock().can_import_snapshot()
545 }
546
547 #[inline(always)]
553 pub fn is_detached(&self) -> bool {
554 self.detached.load(Acquire)
555 }
556
557 pub(crate) fn set_detached(&self, detached: bool) {
558 self.detached.store(detached, Release);
559 }
560
561 #[inline(always)]
562 pub fn peer_id(&self) -> PeerID {
563 self.state
564 .lock()
565 .peer
566 .load(std::sync::atomic::Ordering::Relaxed)
567 }
568
569 #[inline(always)]
570 pub fn detach(&self) {
571 self.with_barrier(|| self.set_detached(true));
572 }
573
574 #[inline(always)]
575 pub fn attach(&self) {
576 self.checkout_to_latest()
577 }
578
579 pub fn state_timestamp(&self) -> Timestamp {
582 let f = { self.state.lock().frontiers.clone() };
584 self.oplog.lock().get_timestamp_of_version(&f)
585 }
586
587 #[inline(always)]
588 pub fn app_state(&self) -> &Arc<LoroMutex<DocState>> {
589 &self.state
590 }
591
592 #[inline]
593 pub fn get_state_deep_value(&self) -> LoroValue {
594 self.state.lock().get_deep_value()
595 }
596
597 #[inline(always)]
598 pub fn oplog(&self) -> &Arc<LoroMutex<OpLog>> {
599 &self.oplog
600 }
601
602 #[inline(always)]
603 pub fn import(&self, bytes: &[u8]) -> Result<ImportStatus, LoroError> {
604 let s = debug_span!("import", peer = self.peer_id());
605 let _e = s.enter();
606 self.import_with(bytes, Default::default())
607 }
608
609 #[inline]
610 pub fn import_with(
611 &self,
612 bytes: &[u8],
613 origin: InternalString,
614 ) -> Result<ImportStatus, LoroError> {
615 self.with_barrier(|| self._import_with(bytes, origin))
616 }
617
618 #[tracing::instrument(skip_all)]
619 fn _import_with(
620 &self,
621 bytes: &[u8],
622 origin: InternalString,
623 ) -> Result<ImportStatus, LoroError> {
624 ensure_cov::notify_cov("loro_internal::import");
625 let parsed = parse_header_and_body(bytes, true)?;
626 loro_common::info!("Importing with mode={:?}", &parsed.mode);
627 let result = match parsed.mode {
628 EncodeMode::OutdatedRle => {
629 if self.state.lock().is_in_txn() {
630 return Err(LoroError::ImportWhenInTxn);
631 }
632
633 let s = tracing::span!(
634 tracing::Level::INFO,
635 "Import updates ",
636 peer = self.peer_id()
637 );
638 let _e = s.enter();
639 self.update_oplog_and_apply_delta_to_state_if_needed(
640 |oplog| oplog.decode(parsed),
641 origin,
642 )
643 }
644 EncodeMode::OutdatedSnapshot => {
645 if self.can_reset_with_snapshot() {
646 loro_common::info!("Init by snapshot {}", self.peer_id());
647 decode_snapshot(self, parsed.mode, parsed.body, origin)
648 } else {
649 self.update_oplog_and_apply_delta_to_state_if_needed(
650 |oplog| oplog.decode(parsed),
651 origin,
652 )
653 }
654 }
655 EncodeMode::FastSnapshot => {
656 if self.can_reset_with_snapshot() {
657 ensure_cov::notify_cov("loro_internal::import::snapshot");
658 loro_common::info!("Init by fast snapshot {}", self.peer_id());
659 decode_snapshot(self, parsed.mode, parsed.body, origin)
660 } else {
661 self.import_changes_and_apply_delta_to_state_if_needed(
662 |oplog| encoding::decode_oplog_changes(oplog, parsed),
663 origin,
664 )
665
666 }
671 }
672 EncodeMode::FastUpdates => self.import_changes_and_apply_delta_to_state_if_needed(
673 |oplog| encoding::decode_oplog_changes(oplog, parsed),
674 origin,
675 ),
676 EncodeMode::Auto => {
677 unreachable!()
678 }
679 };
680
681 self.emit_events();
682
683 result
684 }
685
686 #[tracing::instrument(skip_all)]
687 pub(crate) fn update_oplog_and_apply_delta_to_state_if_needed(
688 &self,
689 f: impl FnOnce(&mut OpLog) -> Result<ImportStatus, LoroError>,
690 origin: InternalString,
691 ) -> Result<ImportStatus, LoroError> {
692 let mut oplog = self.oplog.lock();
693 let owns_rollback = !oplog.has_import_rollback();
699 if owns_rollback {
700 oplog.begin_import_rollback();
701 }
702 if !self.is_detached() {
703 let old_vv = oplog.vv().clone();
704 let old_frontiers = oplog.frontiers().clone();
705 let result = f(&mut oplog);
706 if &old_vv != oplog.vv() {
707 let mut diff = DiffCalculator::new(false);
708 let (diff, diff_mode) = diff.calc_diff_internal(
709 &oplog,
710 &old_vv,
711 &old_frontiers,
712 oplog.vv(),
713 oplog.dag.get_frontiers(),
714 None,
715 );
716 let mut state = self.state.lock();
717 if let Err(e) = state.apply_diff(
718 InternalDocDiff {
719 origin,
720 diff: (diff).into(),
721 by: EventTriggerKind::Import,
722 new_version: Cow::Owned(oplog.frontiers().clone()),
723 },
724 diff_mode,
725 ) {
726 oplog.end_import_rollback(owns_rollback, false);
727 return Err(e);
728 }
729 }
730 match result {
731 Ok(result) => {
732 oplog.end_import_rollback(owns_rollback, true);
733 Ok(result)
734 }
735 Err(e) => {
736 let keep_prefix = &old_vv != oplog.vv();
741 oplog.end_import_rollback(owns_rollback, keep_prefix);
742 Err(e)
743 }
744 }
745 } else {
746 match f(&mut oplog) {
747 Ok(result) => {
748 oplog.end_import_rollback(owns_rollback, true);
749 Ok(result)
750 }
751 Err(e) => {
752 oplog.end_import_rollback(owns_rollback, false);
753 Err(e)
754 }
755 }
756 }
757 }
758
759 #[tracing::instrument(skip_all)]
760 pub(crate) fn import_changes_and_apply_delta_to_state_if_needed(
761 &self,
762 decode_changes: impl FnOnce(&mut OpLog) -> Result<Vec<Change>, LoroError>,
763 origin: InternalString,
764 ) -> Result<ImportStatus, LoroError> {
765 let mut oplog = self.oplog.lock();
766 let arena_checkpoint = oplog.arena.checkpoint_for_rollback();
767 let changes = match decode_changes(&mut oplog) {
768 Ok(changes) => changes,
769 Err(e) => {
770 oplog.arena.rollback(arena_checkpoint);
771 return Err(e);
772 }
773 };
774
775 let preflight = oplog.preflight_import_changes(&changes);
776 if preflight.has_deps_before_shallow_root
777 && (self.is_detached() || !preflight.applies_to_dag)
778 {
779 oplog.arena.rollback(arena_checkpoint);
780 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
781 }
782
783 if self.is_detached() {
784 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
785 if result.has_deps_before_shallow_root {
786 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
787 }
788
789 return Ok(result.status);
790 }
791
792 if !preflight.applies_to_dag {
793 let pending_root_containers = pending_root_containers_to_materialize(&oplog, &changes);
794 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
795 if result.has_deps_before_shallow_root {
796 oplog.arena.rollback(arena_checkpoint);
797 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
798 }
799
800 if !pending_root_containers.is_empty() {
801 let mut state = self.state.lock();
802 for id in pending_root_containers {
803 state.ensure_container(&id);
804 }
805 }
806
807 return Ok(result.status);
808 }
809
810 let old_vv = oplog.vv().clone();
811 let old_frontiers = oplog.frontiers().clone();
812 let isolated_batch = isolated_scalar_root_batch(&oplog, &changes).filter(|(_, names)| {
814 let mut state = self.state.lock();
815 names.iter().all(|name| {
816 let id = ContainerID::new_root(name, ContainerType::Map);
817 !state.store.contains_id(&id)
818 })
819 });
820 let rollback_enabled = preflight.needs_state_apply_rollback;
821 if rollback_enabled {
822 oplog.begin_import_rollback_with_arena(arena_checkpoint);
823 }
824
825 let result = encoding::apply_decoded_changes_to_oplog(&mut oplog, changes);
826 if &old_vv != oplog.vv() {
827 let mut diff = DiffCalculator::new(false);
828 let isolated_batch = isolated_batch.filter(|(component_vv, _)| {
831 component_vv
832 .iter()
833 .all(|(peer, end)| oplog.vv().get(peer) == Some(end))
834 && oplog.vv().iter().all(|(peer, end)| {
835 let old_end = old_vv.get(peer).copied().unwrap_or(0);
836 let component_end = component_vv.get(peer).copied().unwrap_or(0);
837 *end == old_end.max(component_end)
838 })
839 });
840 let (diff, diff_mode) = if let Some((component_vv, _)) = isolated_batch {
841 #[cfg(test)]
842 ISOLATED_FAST_PATH_HITS.with(|hits| hits.set(hits.get() + 1));
843 let component_frontiers = oplog.dag.vv_to_frontiers(&component_vv);
844 let (diff, _) = diff.calc_diff_internal(
845 &oplog,
846 &VersionVector::default(),
847 &Frontiers::default(),
848 &component_vv,
849 &component_frontiers,
850 None,
851 );
852 (diff, DiffMode::Import)
853 } else {
854 diff.calc_diff_internal(
855 &oplog,
856 &old_vv,
857 &old_frontiers,
858 oplog.vv(),
859 oplog.dag.get_frontiers(),
860 None,
861 )
862 };
863 let mut state = self.state.lock();
864 if let Err(e) = state.apply_diff(
865 InternalDocDiff {
866 origin,
867 diff: (diff).into(),
868 by: EventTriggerKind::Import,
869 new_version: Cow::Owned(oplog.frontiers().clone()),
870 },
871 diff_mode,
872 ) {
873 if rollback_enabled {
874 oplog.rollback_import();
875 return Err(e);
876 }
877
878 panic!("state apply returned Err for import without rollback guard: {e}");
879 }
880 }
881
882 if result.has_deps_before_shallow_root {
883 if rollback_enabled {
884 oplog.commit_import_rollback();
885 }
886 return Err(LoroError::ImportUpdatesThatDependsOnOutdatedVersion);
887 }
888
889 if rollback_enabled {
890 oplog.commit_import_rollback();
891 }
892 Ok(result.status)
893 }
894
895 fn emit_events(&self) {
896 let events = {
898 let mut state = self.state.lock();
899 state.take_events()
900 };
901 for event in events {
902 self.observer.emit(event);
903 }
904 }
905
906 pub(crate) fn drop_pending_events(&self) -> Vec<DocDiff> {
907 let mut state = self.state.lock();
908 state.take_events()
909 }
910
911 #[tracing::instrument(skip_all)]
915 pub fn import_json_updates<T: TryInto<JsonSchema>>(&self, json: T) -> LoroResult<ImportStatus> {
916 let json = json.try_into().map_err(|_| LoroError::InvalidJsonSchema)?;
917 self.with_barrier(|| {
918 let result = self.import_changes_and_apply_delta_to_state_if_needed(
919 |oplog| crate::encoding::json_schema::decode_json_changes(json, &oplog.arena),
920 Default::default(),
921 );
922 self.emit_events();
923 result
924 })
925 }
926
927 pub fn export_json_updates(
928 &self,
929 start_vv: &VersionVector,
930 end_vv: &VersionVector,
931 with_peer_compression: bool,
932 ) -> JsonSchema {
933 self.with_barrier(|| {
934 let oplog = self.oplog.lock();
935 let mut start_vv = start_vv;
936 let _temp: Option<VersionVector>;
937 if !oplog.dag.shallow_since_vv().is_empty() {
938 let mut include_all = true;
940 for (peer, counter) in oplog.dag.shallow_since_vv().iter() {
941 if start_vv.get(peer).unwrap_or(&0) < counter {
942 include_all = false;
943 break;
944 }
945 }
946 if !include_all {
947 let mut vv = start_vv.clone();
948 for (&peer, &counter) in oplog.dag.shallow_since_vv().iter() {
949 vv.extend_to_include_end_id(ID::new(peer, counter));
950 }
951 _temp = Some(vv);
952 start_vv = _temp.as_ref().unwrap();
953 }
954 }
955
956 crate::encoding::json_schema::export_json(
957 &oplog,
958 start_vv,
959 end_vv,
960 with_peer_compression,
961 )
962 })
963 }
964
965 pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec<JsonChange> {
966 let oplog = self.oplog.lock();
967 let mut changes = export_json_in_id_span(&oplog, id_span);
968 if let Some(uncommit) = oplog.get_uncommitted_change_in_span(id_span) {
969 let change_json = encode_change(ChangeRef::from_change(&uncommit), &self.arena, None);
970 changes.push(change_json);
971 }
972 changes
973 }
974
975 #[inline]
977 pub fn oplog_vv(&self) -> VersionVector {
978 self.oplog.lock().vv().clone()
979 }
980
981 #[inline]
983 pub fn state_vv(&self) -> VersionVector {
984 let oplog = self.oplog.lock();
985 let f = &self.state.lock().frontiers;
986 oplog.dag.frontiers_to_vv(f).unwrap()
987 }
988
989 pub fn get_by_path(&self, path: &[Index]) -> Option<ValueOrHandler> {
990 let value: LoroValue = self.state.lock().get_value_by_path(path)?;
991 if let LoroValue::Container(c) = value {
992 Some(ValueOrHandler::Handler(Handler::new_attached(
993 c.clone(),
994 self.clone(),
995 )))
996 } else {
997 Some(ValueOrHandler::Value(value))
998 }
999 }
1000
1001 pub fn get_by_str_path(&self, path: &str) -> Option<ValueOrHandler> {
1003 let path = str_to_path(path)?;
1004 self.get_by_path(&path)
1005 }
1006
1007 pub fn get_uncommitted_ops_as_json(&self) -> Option<JsonSchema> {
1008 let arena = &self.arena;
1009 let txn = self.txn.lock();
1010 let txn = txn.as_ref()?;
1011 let ops_ = txn.local_ops();
1012 let new_id = ID {
1013 peer: *txn.peer(),
1014 counter: ops_.first()?.counter,
1015 };
1016 let change = ChangeRef {
1017 id: &new_id,
1018 deps: txn.frontiers(),
1019 timestamp: &txn
1020 .timestamp()
1021 .as_ref()
1022 .copied()
1023 .unwrap_or_else(|| self.oplog.lock().get_timestamp_for_next_txn()),
1024 commit_msg: txn.msg(),
1025 ops: ops_,
1026 lamport: txn.lamport(),
1027 };
1028 let json = encode_change_to_json(change, arena);
1029 Some(json)
1030 }
1031
1032 #[inline]
1033 pub fn get_handler(&self, id: ContainerID) -> Option<Handler> {
1034 if self.has_container(&id) {
1035 self.ensure_root_container(&id);
1036 Some(Handler::new_attached(id, self.clone()))
1037 } else {
1038 None
1039 }
1040 }
1041
1042 #[inline]
1043 fn ensure_root_container(&self, id: &ContainerID) {
1044 if id.is_root() && !id.is_mergeable() {
1048 self.state.lock().ensure_container(id);
1049 }
1050 }
1051
1052 #[inline]
1055 pub fn try_get_text<I: IntoContainerId>(&self, id: I) -> Option<TextHandler> {
1056 let id = id.into_container_id(&self.arena, ContainerType::Text);
1057 if !self.has_container(&id) {
1058 return None;
1059 }
1060 self.ensure_root_container(&id);
1061 Handler::new_attached(id, self.clone()).into_text().ok()
1062 }
1063
1064 #[inline]
1067 pub fn get_text<I: IntoContainerId>(&self, id: I) -> TextHandler {
1068 self.try_get_text(id)
1069 .expect("The container does not exist in the document. Use `try_get_text` or `get_container` to check for existence.")
1070 }
1071
1072 #[inline]
1075 pub fn try_get_list<I: IntoContainerId>(&self, id: I) -> Option<ListHandler> {
1076 let id = id.into_container_id(&self.arena, ContainerType::List);
1077 if !self.has_container(&id) {
1078 return None;
1079 }
1080 self.ensure_root_container(&id);
1081 Handler::new_attached(id, self.clone()).into_list().ok()
1082 }
1083
1084 #[inline]
1087 pub fn get_list<I: IntoContainerId>(&self, id: I) -> ListHandler {
1088 self.try_get_list(id)
1089 .expect("The container does not exist in the document. Use `try_get_list` or `get_container` to check for existence.")
1090 }
1091
1092 #[inline]
1095 pub fn try_get_movable_list<I: IntoContainerId>(&self, id: I) -> Option<MovableListHandler> {
1096 let id = id.into_container_id(&self.arena, ContainerType::MovableList);
1097 if !self.has_container(&id) {
1098 return None;
1099 }
1100 self.ensure_root_container(&id);
1101 Handler::new_attached(id, self.clone())
1102 .into_movable_list()
1103 .ok()
1104 }
1105
1106 #[inline]
1109 pub fn get_movable_list<I: IntoContainerId>(&self, id: I) -> MovableListHandler {
1110 self.try_get_movable_list(id)
1111 .expect("The container does not exist in the document. Use `try_get_movable_list` or `get_container` to check for existence.")
1112 }
1113
1114 #[inline]
1117 pub fn try_get_map<I: IntoContainerId>(&self, id: I) -> Option<MapHandler> {
1118 let id = id.into_container_id(&self.arena, ContainerType::Map);
1119 if !self.has_container(&id) {
1120 return None;
1121 }
1122 self.ensure_root_container(&id);
1123 Handler::new_attached(id, self.clone()).into_map().ok()
1124 }
1125
1126 #[inline]
1129 pub fn get_map<I: IntoContainerId>(&self, id: I) -> MapHandler {
1130 self.try_get_map(id)
1131 .expect("The container does not exist in the document. Use `try_get_map` or `get_container` to check for existence.")
1132 }
1133
1134 #[inline]
1137 pub fn try_get_tree<I: IntoContainerId>(&self, id: I) -> Option<TreeHandler> {
1138 let id = id.into_container_id(&self.arena, ContainerType::Tree);
1139 if !self.has_container(&id) {
1140 return None;
1141 }
1142 self.ensure_root_container(&id);
1143 Handler::new_attached(id, self.clone()).into_tree().ok()
1144 }
1145
1146 #[inline]
1149 pub fn get_tree<I: IntoContainerId>(&self, id: I) -> TreeHandler {
1150 self.try_get_tree(id)
1151 .expect("The container does not exist in the document. Use `try_get_tree` or `get_container` to check for existence.")
1152 }
1153
1154 #[cfg(feature = "counter")]
1155 pub fn try_get_counter<I: IntoContainerId>(
1156 &self,
1157 id: I,
1158 ) -> Option<crate::handler::counter::CounterHandler> {
1159 let id = id.into_container_id(&self.arena, ContainerType::Counter);
1160 if !self.has_container(&id) {
1161 return None;
1162 }
1163 self.ensure_root_container(&id);
1164 Handler::new_attached(id, self.clone()).into_counter().ok()
1165 }
1166
1167 #[cfg(feature = "counter")]
1168 pub fn get_counter<I: IntoContainerId>(
1169 &self,
1170 id: I,
1171 ) -> crate::handler::counter::CounterHandler {
1172 self.try_get_counter(id)
1173 .expect("The container does not exist in the document. Use `try_get_counter` or `get_container` to check for existence.")
1174 }
1175
1176 #[must_use]
1177 pub fn has_container(&self, id: &ContainerID) -> bool {
1178 if id.is_root() && !id.is_mergeable() {
1179 return true;
1180 }
1181
1182 let exist = self.state.lock().does_container_exist(id);
1183 exist
1184 }
1185
1186 #[instrument(level = "info", skip_all)]
1200 pub fn undo_internal(
1201 &self,
1202 id_span: IdSpan,
1203 container_remap: &mut FxHashMap<ContainerID, ContainerID>,
1204 post_transform_base: Option<&DiffBatch>,
1205 before_diff: &mut dyn FnMut(&DiffBatch),
1206 ) -> LoroResult<CommitWhenDrop<'_>> {
1207 if !self.can_edit() {
1208 return Err(LoroError::EditWhenDetached);
1209 }
1210
1211 let (options, txn) = self.implicit_commit_then_stop();
1212 if !self.oplog().lock().vv().includes_id(id_span.id_last()) {
1213 self.renew_txn_if_auto_commit(options);
1214 return Err(LoroError::UndoInvalidIdSpan(id_span.id_last()));
1215 }
1216
1217 let (was_recording, latest_frontiers) = {
1218 let mut state = self.state.lock();
1219 let was_recording = state.is_recording();
1220 state.stop_and_clear_recording();
1221 (was_recording, state.frontiers.clone())
1222 };
1223
1224 let spans = self.oplog.lock().split_span_based_on_deps(id_span);
1225 let diff = crate::undo::undo(
1226 spans,
1227 match post_transform_base {
1228 Some(d) => Either::Right(d),
1229 None => Either::Left(&latest_frontiers),
1230 },
1231 |from, to| {
1232 self._checkout_without_emitting(from, false, false).unwrap();
1233 self.state.lock().start_recording();
1234 self._checkout_without_emitting(to, false, false).unwrap();
1235 let mut state = self.state.lock();
1236 let e = state.take_events();
1237 state.stop_and_clear_recording();
1238 DiffBatch::new(e)
1239 },
1240 before_diff,
1241 );
1242
1243 self._checkout_without_emitting(&latest_frontiers, false, false)?;
1247 self.set_detached(false);
1248 if was_recording {
1249 self.state.lock().start_recording();
1250 }
1251 drop(txn);
1252 self.start_auto_commit();
1253 if let Err(e) = self._apply_diff(diff, container_remap, true) {
1257 warn!("Undo Failed {:?}", e);
1258 }
1259
1260 if let Some(options) = options {
1261 self.set_next_commit_options(options);
1262 }
1263 Ok(CommitWhenDrop {
1264 doc: self,
1265 default_options: CommitOptions::new().origin("undo"),
1266 })
1267 }
1268
1269 pub fn revert_to(&self, target: &Frontiers) -> LoroResult<()> {
1275 let f = self.state_frontiers();
1278 let diff = self.diff(&f, target)?;
1279 self._apply_diff(diff, &mut Default::default(), false)
1280 }
1281
1282 pub fn diff(&self, a: &Frontiers, b: &Frontiers) -> LoroResult<DiffBatch> {
1287 {
1288 let oplog = self.oplog.lock();
1291 let validate_frontiers = |frontiers: &Frontiers| -> LoroResult<()> {
1292 for id in frontiers.iter() {
1293 if !oplog.dag.contains(id) {
1294 return Err(LoroError::FrontiersNotFound(id));
1295 }
1296 }
1297
1298 if oplog.dag.is_before_shallow_root(frontiers) {
1299 return Err(LoroError::SwitchToVersionBeforeShallowRoot);
1300 }
1301
1302 Ok(())
1303 };
1304
1305 validate_frontiers(a)?;
1306 validate_frontiers(b)?;
1307 }
1308
1309 let (options, txn) = self.implicit_commit_then_stop();
1310 let was_detached = self.is_detached();
1311 let old_frontiers = self.state_frontiers();
1312 let was_recording = {
1313 let mut state = self.state.lock();
1314 let is_recording = state.is_recording();
1315 state.stop_and_clear_recording();
1316 is_recording
1317 };
1318 let result = (|| {
1319 self._checkout_without_emitting(a, true, false)?;
1320 self.state.lock().start_recording();
1321 self._checkout_without_emitting(b, true, false)?;
1322 let mut state = self.state.lock();
1323 let e = state.take_events();
1324 state.stop_and_clear_recording();
1325 Ok::<_, LoroError>(e)
1326 })();
1327
1328 self._checkout_without_emitting(&old_frontiers, false, false)
1330 .unwrap();
1331 drop(txn);
1332 if !was_detached {
1333 self.set_detached(false);
1334 self.renew_txn_if_auto_commit(options);
1335 }
1336 if was_recording {
1337 self.state.lock().start_recording();
1338 }
1339 result.map(DiffBatch::new)
1340 }
1341
1342 #[inline(always)]
1344 pub fn apply_diff(&self, diff: DiffBatch) -> LoroResult<()> {
1345 self._apply_diff(diff, &mut Default::default(), true)
1346 }
1347
1348 pub(crate) fn _apply_diff(
1360 &self,
1361 diff: DiffBatch,
1362 container_remap: &mut FxHashMap<ContainerID, ContainerID>,
1363 skip_unreachable: bool,
1364 ) -> LoroResult<()> {
1365 if !self.can_edit() {
1366 return Err(LoroError::EditWhenDetached);
1367 }
1368
1369 let mut ans: LoroResult<()> = Ok(());
1370 let mut missing_containers: Vec<ContainerID> = Vec::new();
1371 for (mut id, diff) in diff.into_iter() {
1372 let mut remapped = false;
1373 while let Some(rid) = container_remap.get(&id) {
1374 remapped = true;
1375 id = rid.clone();
1376 }
1377
1378 if matches!(&id, ContainerID::Normal { .. }) && self.arena.id_to_idx(&id).is_none() {
1379 let exists = self.state.lock().does_container_exist(&id);
1381 if !exists {
1382 missing_containers.push(id);
1383 continue;
1384 }
1385 self.state.lock().ensure_container(&id);
1387 }
1388
1389 if skip_unreachable && !remapped && !self.state.lock().get_reachable(&id) {
1390 continue;
1391 }
1392
1393 let Some(h) = self.get_handler(id.clone()) else {
1394 return Err(LoroError::ContainersNotFound {
1395 containers: Box::new(vec![id]),
1396 });
1397 };
1398 if let Err(e) = h.apply_diff(diff, container_remap) {
1399 ans = Err(e);
1400 }
1401 }
1402
1403 if !missing_containers.is_empty() {
1404 return Err(LoroError::ContainersNotFound {
1405 containers: Box::new(missing_containers),
1406 });
1407 }
1408
1409 ans
1410 }
1411
1412 #[inline]
1414 pub fn diagnose_size(&self) {
1415 self.oplog().lock().diagnose_size();
1416 }
1417
1418 #[inline]
1419 pub fn oplog_frontiers(&self) -> Frontiers {
1420 self.oplog().lock().frontiers().clone()
1421 }
1422
1423 #[inline]
1424 pub fn state_frontiers(&self) -> Frontiers {
1425 self.state.lock().frontiers.clone()
1426 }
1427
1428 #[inline]
1432 pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering {
1433 self.oplog().lock().cmp_with_frontiers(other)
1434 }
1435
1436 #[inline]
1440 pub fn cmp_frontiers(
1441 &self,
1442 a: &Frontiers,
1443 b: &Frontiers,
1444 ) -> Result<Option<Ordering>, FrontiersNotIncluded> {
1445 self.oplog().lock().cmp_frontiers(a, b)
1446 }
1447
1448 pub fn subscribe_root(&self, callback: Subscriber) -> Subscription {
1449 let mut state = self.state.lock();
1450 if !state.is_recording() {
1451 state.start_recording();
1452 }
1453
1454 self.observer.subscribe_root(callback)
1455 }
1456
1457 pub fn subscribe(&self, container_id: &ContainerID, callback: Subscriber) -> Subscription {
1458 let mut state = self.state.lock();
1459 if !state.is_recording() {
1460 state.start_recording();
1461 }
1462
1463 self.observer.subscribe(container_id, callback)
1464 }
1465
1466 pub fn subscribe_local_update(&self, callback: LocalUpdateCallback) -> Subscription {
1467 let (sub, activate) = self.local_update_subs.inner().insert((), callback);
1468 activate();
1469 sub
1470 }
1471
1472 #[tracing::instrument(skip_all)]
1474 pub fn import_batch(&self, bytes: &[Vec<u8>]) -> LoroResult<ImportStatus> {
1475 if bytes.is_empty() {
1476 return Ok(ImportStatus::default());
1477 }
1478
1479 if bytes.len() == 1 {
1480 return self.import(&bytes[0]);
1481 }
1482
1483 let mut success = VersionRange::default();
1484 let mut meta_arr = bytes
1485 .iter()
1486 .map(|b| Ok((LoroDoc::decode_import_blob_meta(b, false)?, b)))
1487 .collect::<LoroResult<Vec<(ImportBlobMetadata, &Vec<u8>)>>>()?;
1488 meta_arr.sort_by(|a, b| {
1489 a.0.mode
1490 .cmp(&b.0.mode)
1491 .then(b.0.change_num.cmp(&a.0.change_num))
1492 });
1493
1494 let (options, txn) = self.implicit_commit_then_stop();
1495 let was_attached = !self.is_detached();
1526 self.set_detached(true);
1527 {
1528 let mut oplog = self.oplog.lock();
1529 oplog.batch_importing = true;
1530 if was_attached {
1536 oplog.begin_import_rollback();
1537 }
1538 }
1539
1540 let mut guard = BatchImportGuard {
1541 doc: self,
1542 was_attached,
1543 txn: Some(txn),
1544 options,
1545 #[cfg(debug_assertions)]
1546 finished: false,
1547 };
1548
1549 let loop_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1554 let mut err = None;
1555 for (_meta, data) in meta_arr {
1556 #[cfg(test)]
1557 take_panic_at_batch_import_blob_for_test();
1558 match guard.doc._import_with(data, Default::default()) {
1559 Ok(s) => {
1560 for (peer, (start, end)) in s.success.iter() {
1561 match success.0.entry(*peer) {
1562 Entry::Occupied(mut e) => {
1563 e.get_mut().1 = *end.max(&e.get().1);
1564 }
1565 Entry::Vacant(e) => {
1566 e.insert((*start, *end));
1567 }
1568 }
1569 }
1570 }
1571 Err(e) => {
1572 err = Some(e);
1573 }
1574 }
1575 }
1576 err
1577 }));
1578
1579 let err = match loop_result {
1580 Ok(err) => err,
1581 Err(payload) => {
1582 let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1586 let _ = guard.finish();
1587 }));
1588 std::panic::resume_unwind(payload);
1589 }
1590 };
1591
1592 let pending = guard.finish()?;
1593 if let Some(err) = err {
1594 return Err(err);
1595 }
1596
1597 Ok(ImportStatus {
1598 success,
1599 pending: if pending.is_empty() {
1600 None
1601 } else {
1602 Some(pending)
1603 },
1604 })
1605 }
1606
1607 #[inline]
1609 pub fn get_value(&self) -> LoroValue {
1610 self.state.lock().get_value()
1611 }
1612
1613 #[inline]
1615 pub fn get_deep_value(&self) -> LoroValue {
1616 self.state.lock().get_deep_value()
1617 }
1618
1619 #[inline]
1621 pub fn get_deep_value_with_id(&self) -> LoroValue {
1622 self.state.lock().get_deep_value_with_id()
1623 }
1624
1625 pub fn checkout_to_latest(&self) {
1626 let (options, _guard) = self.implicit_commit_then_stop();
1627 if !self.is_detached() {
1628 drop(_guard);
1629 self.renew_txn_if_auto_commit(options);
1630 return;
1631 }
1632
1633 self._checkout_to_latest_without_commit(true)
1634 .expect("checkout to oplog frontiers should succeed");
1635 self.emit_events();
1636 drop(_guard);
1637 self.renew_txn_if_auto_commit(options);
1638 }
1639
1640 pub(crate) fn _checkout_to_latest_without_commit(
1642 &self,
1643 to_commit_then_renew: bool,
1644 ) -> LoroResult<()> {
1645 self._checkout_to_latest_without_commit_with_event(
1646 to_commit_then_renew,
1647 "checkout".into(),
1648 EventTriggerKind::Checkout,
1649 )
1650 }
1651
1652 pub(crate) fn _checkout_to_latest_without_commit_as_import(
1653 &self,
1654 to_commit_then_renew: bool,
1655 origin: InternalString,
1656 ) -> LoroResult<()> {
1657 self._checkout_to_latest_without_commit_with_event(
1658 to_commit_then_renew,
1659 origin,
1660 EventTriggerKind::Import,
1661 )
1662 }
1663
1664 fn _checkout_to_latest_without_commit_with_event(
1665 &self,
1666 to_commit_then_renew: bool,
1667 origin: InternalString,
1668 triggered_by: EventTriggerKind,
1669 ) -> LoroResult<()> {
1670 tracing::info_span!("CheckoutToLatest", peer = self.peer_id()).in_scope(|| {
1671 let f = self.oplog_frontiers();
1672 let this = &self;
1673 let frontiers = &f;
1674 this._checkout_without_emitting_with_event(
1675 frontiers,
1676 false,
1677 to_commit_then_renew,
1678 origin,
1679 triggered_by,
1680 )?;
1681 this.emit_events();
1683 if this.config.detached_editing() {
1684 this.renew_peer_id();
1685 }
1686
1687 self.set_detached(false);
1688 Ok(())
1689 })
1690 }
1691
1692 pub fn checkout(&self, frontiers: &Frontiers) -> LoroResult<()> {
1697 let was_detached = self.is_detached();
1698 let (options, guard) = self.implicit_commit_then_stop();
1699 let result = self._checkout_without_emitting(frontiers, true, true);
1700 if result.is_ok() {
1701 self.emit_events();
1702 }
1703 drop(guard);
1704 if self.config.detached_editing() {
1705 if result.is_ok() {
1706 self.renew_peer_id();
1707 }
1708 self.renew_txn_if_auto_commit(options);
1709 } else if result.is_err() {
1710 if !was_detached {
1711 self.renew_txn_if_auto_commit(options);
1712 }
1713 } else if !self.is_detached() {
1714 self.renew_txn_if_auto_commit(options);
1715 }
1716
1717 result
1718 }
1719
1720 #[instrument(level = "info", skip(self))]
1722 pub(crate) fn _checkout_without_emitting(
1723 &self,
1724 frontiers: &Frontiers,
1725 to_shrink_frontiers: bool,
1726 to_commit_then_renew: bool,
1727 ) -> Result<(), LoroError> {
1728 self._checkout_without_emitting_with_event(
1729 frontiers,
1730 to_shrink_frontiers,
1731 to_commit_then_renew,
1732 "checkout".into(),
1733 EventTriggerKind::Checkout,
1734 )
1735 }
1736
1737 fn _checkout_without_emitting_with_event(
1738 &self,
1739 frontiers: &Frontiers,
1740 to_shrink_frontiers: bool,
1741 _to_commit_then_renew: bool,
1742 origin: InternalString,
1743 triggered_by: EventTriggerKind,
1744 ) -> Result<(), LoroError> {
1745 if !self.txn.is_locked() {
1746 return Err(LoroError::TransactionError(
1747 "checkout requires the transaction mutex to be held"
1748 .to_string()
1749 .into_boxed_str(),
1750 ));
1751 }
1752 let from_frontiers = self.state_frontiers();
1753 loro_common::info!(
1754 "checkout from={:?} to={:?} cur_vv={:?}",
1755 from_frontiers,
1756 frontiers,
1757 self.oplog_vv()
1758 );
1759
1760 if &from_frontiers == frontiers {
1761 self.set_detached(frontiers != &self.oplog_frontiers());
1762 return Ok(());
1763 }
1764
1765 let oplog = self.oplog.lock();
1766 if oplog.dag.is_before_shallow_root(frontiers) {
1767 return Err(LoroError::SwitchToVersionBeforeShallowRoot);
1768 }
1769
1770 let frontiers = if to_shrink_frontiers {
1771 shrink_frontiers(frontiers, &oplog.dag).map_err(LoroError::FrontiersNotFound)?
1772 } else {
1773 frontiers.clone()
1774 };
1775
1776 if from_frontiers == frontiers {
1777 return Ok(());
1778 }
1779
1780 let mut state = self.state.lock();
1781 let mut calc = self.diff_calculator.lock();
1782 for i in frontiers.iter() {
1783 if !oplog.dag.contains(i) {
1784 return Err(LoroError::FrontiersNotFound(i));
1785 }
1786 }
1787
1788 let before = oplog.dag.frontiers_to_vv(&state.frontiers).ok_or_else(|| {
1789 LoroError::NotFoundError(
1790 format!(
1791 "Cannot find the current state version {:?}",
1792 state.frontiers
1793 )
1794 .into_boxed_str(),
1795 )
1796 })?;
1797 let Some(after) = &oplog.dag.frontiers_to_vv(&frontiers) else {
1798 return Err(LoroError::NotFoundError(
1799 format!("Cannot find the specified version {:?}", frontiers).into_boxed_str(),
1800 ));
1801 };
1802
1803 self.set_detached(true);
1804 let (diff, diff_mode) =
1805 calc.calc_diff_internal(&oplog, &before, &state.frontiers, after, &frontiers, None);
1806 state.apply_diff(
1807 InternalDocDiff {
1808 origin,
1809 diff: Cow::Owned(diff),
1810 by: triggered_by,
1811 new_version: Cow::Owned(frontiers.clone()),
1812 },
1813 diff_mode,
1814 )?;
1815
1816 Ok(())
1817 }
1818
1819 #[inline]
1820 pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers {
1821 self.oplog.lock().dag.vv_to_frontiers(vv)
1822 }
1823
1824 #[inline]
1825 pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option<VersionVector> {
1826 self.oplog.lock().dag.frontiers_to_vv(frontiers)
1827 }
1828
1829 pub fn merge(&self, other: &Self) -> LoroResult<ImportStatus> {
1833 let updates = other.export(ExportMode::updates(&self.oplog_vv())).unwrap();
1834 self.import(&updates)
1835 }
1836
1837 pub(crate) fn arena(&self) -> &SharedArena {
1838 &self.arena
1839 }
1840
1841 #[inline]
1842 pub fn len_ops(&self) -> usize {
1843 if self.oplog.can_lock_in_this_thread() {
1844 return self.oplog.lock().visible_op_count_exact();
1845 }
1846
1847 self.visible_op_count.load(Acquire)
1848 }
1849
1850 #[inline]
1851 pub fn len_changes(&self) -> usize {
1852 let oplog = self.oplog.lock();
1853 oplog.len_changes()
1854 }
1855
1856 pub fn config(&self) -> &Configure {
1857 &self.config
1858 }
1859
1860 pub fn check_state_diff_calc_consistency_slow(&self) {
1865 {
1867 static IS_CHECKING: std::sync::atomic::AtomicBool =
1868 std::sync::atomic::AtomicBool::new(false);
1869 if IS_CHECKING.load(std::sync::atomic::Ordering::Acquire) {
1870 return;
1871 }
1872
1873 IS_CHECKING.store(true, std::sync::atomic::Ordering::Release);
1874 let peer_id = self.peer_id();
1875 let s = info_span!("CheckStateDiffCalcConsistencySlow", ?peer_id);
1876 let _g = s.enter();
1877 let options = self.implicit_commit_then_stop().0;
1878 self.oplog.lock().check_dag_correctness();
1879 if self.is_shallow() {
1880 let initial_snapshot = self
1891 .export(ExportMode::state_only(Some(
1892 &self.shallow_since_frontiers(),
1893 )))
1894 .unwrap();
1895
1896 let doc = LoroDoc::new();
1898 doc.import(&initial_snapshot).unwrap();
1899 self.checkout(&self.shallow_since_frontiers()).unwrap();
1900 assert_eq!(self.get_deep_value(), doc.get_deep_value());
1901
1902 let updates = self.export(ExportMode::all_updates()).unwrap();
1904
1905 doc.import(&updates).unwrap();
1907 self.checkout_to_latest();
1908
1909 assert_eq!(doc.get_deep_value(), self.get_deep_value());
1912 let mut calculated_state = doc.app_state().lock();
1913 let mut current_state = self.app_state().lock();
1914 current_state.check_is_the_same(&mut calculated_state);
1915 } else {
1916 let f = self.state_frontiers();
1917 let vv = self.oplog().lock().dag.frontiers_to_vv(&f).unwrap();
1918 let bytes = self.export(ExportMode::updates_till(&vv)).unwrap();
1919 let doc = Self::new();
1920 doc.import(&bytes).unwrap();
1921 let mut calculated_state = doc.app_state().lock();
1922 let mut current_state = self.app_state().lock();
1923 current_state.check_is_the_same(&mut calculated_state);
1924 }
1925
1926 self.renew_txn_if_auto_commit(options);
1927 IS_CHECKING.store(false, std::sync::atomic::Ordering::Release);
1928 }
1929 }
1930
1931 pub fn query_pos(&self, pos: &Cursor) -> Result<PosQueryResult, CannotFindRelativePosition> {
1932 self.query_pos_internal(pos, true)
1933 }
1934
1935 pub(crate) fn query_pos_internal(
1937 &self,
1938 pos: &Cursor,
1939 ret_event_index: bool,
1940 ) -> Result<PosQueryResult, CannotFindRelativePosition> {
1941 if !self.has_container(&pos.container) {
1942 return Err(CannotFindRelativePosition::IdNotFound);
1943 }
1944
1945 let mut state = self.state.lock();
1946 if let Some(ans) = state.get_relative_position(pos, ret_event_index) {
1947 Ok(PosQueryResult {
1948 update: None,
1949 current: AbsolutePosition {
1950 pos: ans,
1951 side: pos.side,
1952 },
1953 })
1954 } else {
1955 drop(state);
1967 let result = self.with_barrier(|| {
1968 let oplog = self.oplog().lock();
1969 if let Some(id) = pos.id {
1971 if oplog.arena.id_to_idx(&pos.container).is_none() {
1973 let mut s = self.state.lock();
1974 if !s.does_container_exist(&pos.container) {
1975 return Err(CannotFindRelativePosition::ContainerDeleted);
1976 }
1977 s.ensure_container(&pos.container);
1978 drop(s);
1979 }
1980 let idx = oplog.arena.id_to_idx(&pos.container).unwrap();
1981 let Some(delete_op_id) = find_last_delete_op(&oplog, id, idx) else {
1983 if oplog.shallow_since_vv().includes_id(id) {
1984 return Err(CannotFindRelativePosition::HistoryCleared);
1985 }
1986
1987 tracing::error!("Cannot find id {}", id);
1988 return Err(CannotFindRelativePosition::IdNotFound);
1989 };
1990 let mut diff_calc = DiffCalculator::new(true);
1992 let before_frontiers: Frontiers = oplog.dag.find_deps_of_id(delete_op_id);
1993 let before = &oplog.dag.frontiers_to_vv(&before_frontiers).unwrap();
1994 diff_calc.calc_diff_internal(
1996 &oplog,
1997 before,
1998 &before_frontiers,
1999 oplog.vv(),
2000 oplog.frontiers(),
2001 Some(&|target| idx == target),
2002 );
2003 let depth = self.arena.get_depth(idx);
2005 let (_, diff_calc) = &mut diff_calc.get_or_create_calc(idx, depth);
2006 match diff_calc {
2007 crate::diff_calc::ContainerDiffCalculator::Richtext(text) => {
2008 let c = text.get_id_latest_pos(id).unwrap();
2009 let new_pos = c.pos;
2010 let handler = self.get_text(&pos.container);
2011 let current_pos = handler.convert_entity_index_to_event_index(new_pos);
2012 Ok(PosQueryResult {
2013 update: handler.get_cursor(current_pos, c.side),
2014 current: AbsolutePosition {
2015 pos: current_pos,
2016 side: c.side,
2017 },
2018 })
2019 }
2020 crate::diff_calc::ContainerDiffCalculator::List(list) => {
2021 let c = list.get_id_latest_pos(id).unwrap();
2022 let new_pos = c.pos;
2023 let handler = self.get_list(&pos.container);
2024 Ok(PosQueryResult {
2025 update: handler.get_cursor(new_pos, c.side),
2026 current: AbsolutePosition {
2027 pos: new_pos,
2028 side: c.side,
2029 },
2030 })
2031 }
2032 crate::diff_calc::ContainerDiffCalculator::MovableList(list) => {
2033 let c = list.get_id_latest_pos(id).unwrap();
2034 let new_pos = c.pos;
2035 let handler = self.get_movable_list(&pos.container);
2036 let new_pos = handler.op_pos_to_user_pos(new_pos);
2037 Ok(PosQueryResult {
2038 update: handler.get_cursor(new_pos, c.side),
2039 current: AbsolutePosition {
2040 pos: new_pos,
2041 side: c.side,
2042 },
2043 })
2044 }
2045 crate::diff_calc::ContainerDiffCalculator::Tree(_) => unreachable!(),
2046 crate::diff_calc::ContainerDiffCalculator::Map(_) => unreachable!(),
2047 #[cfg(feature = "counter")]
2048 crate::diff_calc::ContainerDiffCalculator::Counter(_) => unreachable!(),
2049 crate::diff_calc::ContainerDiffCalculator::Unknown(_) => unreachable!(),
2050 }
2051 } else {
2052 match pos.container.container_type() {
2053 ContainerType::Text => {
2054 let text = self.get_text(&pos.container);
2055 Ok(PosQueryResult {
2056 update: Some(Cursor {
2057 id: None,
2058 container: text.id(),
2059 side: pos.side,
2060 origin_pos: text.len_unicode(),
2061 }),
2062 current: AbsolutePosition {
2063 pos: text.len_event(),
2064 side: pos.side,
2065 },
2066 })
2067 }
2068 ContainerType::List => {
2069 let list = self.get_list(&pos.container);
2070 Ok(PosQueryResult {
2071 update: Some(Cursor {
2072 id: None,
2073 container: list.id(),
2074 side: pos.side,
2075 origin_pos: list.len(),
2076 }),
2077 current: AbsolutePosition {
2078 pos: list.len(),
2079 side: pos.side,
2080 },
2081 })
2082 }
2083 ContainerType::MovableList => {
2084 let list = self.get_movable_list(&pos.container);
2085 Ok(PosQueryResult {
2086 update: Some(Cursor {
2087 id: None,
2088 container: list.id(),
2089 side: pos.side,
2090 origin_pos: list.len(),
2091 }),
2092 current: AbsolutePosition {
2093 pos: list.len(),
2094 side: pos.side,
2095 },
2096 })
2097 }
2098 ContainerType::Map | ContainerType::Tree | ContainerType::Unknown(_) => {
2099 unreachable!()
2100 }
2101 #[cfg(feature = "counter")]
2102 ContainerType::Counter => unreachable!(),
2103 }
2104 }
2105 });
2106 result
2107 }
2108 }
2109
2110 pub fn free_history_cache(&self) {
2115 self.oplog.lock().free_history_cache();
2116 }
2117
2118 pub fn free_diff_calculator(&self) {
2120 *self.diff_calculator.lock() = DiffCalculator::new(true);
2121 }
2122
2123 pub fn has_history_cache(&self) -> bool {
2126 self.oplog.lock().has_history_cache()
2127 }
2128
2129 #[inline]
2133 pub fn compact_change_store(&self) {
2134 self.with_barrier(|| {
2135 self.oplog.lock().compact_change_store();
2136 });
2137 }
2138
2139 #[inline]
2143 pub fn analyze(&self) -> DocAnalysis {
2144 DocAnalysis::analyze(self)
2145 }
2146
2147 pub fn get_path_to_container(&self, id: &ContainerID) -> Option<Vec<(ContainerID, Index)>> {
2149 let mut state = self.state.lock();
2150 if state.arena.id_to_idx(id).is_none() {
2151 if id.is_mergeable() {
2152 state.arena.register_container(id);
2156 } else if !state.does_container_exist(id) {
2157 return None;
2158 } else {
2159 state.ensure_container(id);
2160 }
2161 }
2162 let idx = state.arena.id_to_idx(id).unwrap();
2163 state.get_path(idx)
2164 }
2165
2166 #[instrument(skip(self))]
2167 pub fn export(&self, mode: ExportMode) -> Result<Vec<u8>, LoroEncodeError> {
2168 self.with_barrier(|| {
2169 let ans = match mode {
2170 ExportMode::Snapshot => export_fast_snapshot(self)?,
2171 ExportMode::Updates { from } => export_fast_updates(self, &from),
2172 ExportMode::UpdatesInRange { spans } => {
2173 export_fast_updates_in_range(&self.oplog.lock(), spans.as_ref())
2174 }
2175 ExportMode::ShallowSnapshot(f) => export_shallow_snapshot(self, &f)?,
2176 ExportMode::StateOnly(f) => match f {
2177 Some(f) => export_state_only_snapshot(self, &f)?,
2178 None => export_state_only_snapshot(self, &self.oplog_frontiers())?,
2179 },
2180 ExportMode::SnapshotAt { version } => export_snapshot_at(self, &version)?,
2181 };
2182 Ok(ans)
2183 })
2184 }
2185
2186 pub fn shallow_since_vv(&self) -> ImVersionVector {
2192 self.oplog().lock().shallow_since_vv().clone()
2193 }
2194
2195 pub fn shallow_since_frontiers(&self) -> Frontiers {
2196 self.oplog().lock().shallow_since_frontiers().clone()
2197 }
2198
2199 pub fn is_shallow(&self) -> bool {
2201 !self.oplog().lock().shallow_since_vv().is_empty()
2202 }
2203
2204 pub fn get_pending_txn_len(&self) -> usize {
2209 if let Some(txn) = self.txn.lock().as_ref() {
2210 txn.len()
2211 } else {
2212 0
2213 }
2214 }
2215
2216 #[inline]
2217 pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff {
2218 self.oplog().lock().dag.find_path(from, to)
2219 }
2220
2221 pub fn subscribe_first_commit_from_peer(
2227 &self,
2228 callback: FirstCommitFromPeerCallback,
2229 ) -> Subscription {
2230 let (s, enable) = self
2231 .first_commit_from_peer_subs
2232 .inner()
2233 .insert((), callback);
2234 enable();
2235 s
2236 }
2237
2238 pub fn subscribe_pre_commit(&self, callback: PreCommitCallback) -> Subscription {
2243 let (s, enable) = self.pre_commit_subs.inner().insert((), callback);
2244 enable();
2245 s
2246 }
2247}
2248
2249fn pending_root_containers_to_materialize(oplog: &OpLog, changes: &[Change]) -> Vec<ContainerID> {
2250 let mut roots = FxHashSet::default();
2251 for change in changes {
2252 if change.ctr_end() <= oplog.vv().get(&change.id.peer).copied().unwrap_or(0) {
2253 continue;
2254 }
2255
2256 if oplog.dag.is_before_shallow_root(&change.deps)
2257 || oplog
2258 .dag
2259 .get_change_lamport_from_deps(&change.deps)
2260 .is_some()
2261 {
2262 continue;
2263 }
2264
2265 for op in change.ops.iter() {
2266 let id = oplog
2267 .arena
2268 .get_container_id(op.container)
2269 .expect("decoded op container should be registered");
2270 if id.is_root() && !id.is_mergeable() {
2279 roots.insert(id);
2280 }
2281 }
2282 }
2283
2284 roots.into_iter().collect()
2285}
2286
2287fn isolated_scalar_root_batch(
2291 oplog: &OpLog,
2292 changes: &[Change],
2293) -> Option<(VersionVector, FxHashSet<InternalString>)> {
2294 if changes.is_empty() || !oplog.shallow_since_vv().is_empty() {
2295 return None;
2296 }
2297
2298 let mut component_vv = VersionVector::new();
2301 let mut root_names = FxHashSet::default();
2302 for change in changes {
2303 let peer = change.id.peer;
2304 if oplog.vv().get(&peer).copied().unwrap_or(0) != 0 {
2305 return None;
2306 }
2307 let end = component_vv.entry(peer).or_insert(0);
2308 if change.id.counter != *end {
2309 return None;
2310 }
2311 *end = change.ctr_end();
2312
2313 for op in change.ops.iter() {
2314 let cid = oplog.arena.idx_to_id(op.container)?;
2315 if cid.is_mergeable() {
2316 return None;
2317 }
2318 let ContainerID::Root {
2319 name,
2320 container_type: ContainerType::Map,
2321 } = cid
2322 else {
2323 return None;
2324 };
2325 let InnerContent::Map(map) = &op.content else {
2326 return None;
2327 };
2328 if let Some(value) = &map.value {
2329 if value.is_container()
2330 || loro_common::parse_mergeable_marker(
2331 &ContainerID::new_root(&name, ContainerType::Map),
2332 &map.key,
2333 value,
2334 )
2335 .is_some()
2336 {
2337 return None;
2338 }
2339 }
2340 root_names.insert(name);
2341 }
2342 }
2343
2344 for change in changes {
2346 for dep in change.deps.iter() {
2347 if dep.counter >= component_vv.get(&dep.peer).copied().unwrap_or(0) {
2348 return None;
2349 }
2350 }
2351 }
2352
2353 if oplog
2354 .change_store()
2355 .old_history_may_touch_root_names(&root_names)
2356 {
2357 return None;
2358 }
2359
2360 Some((component_vv, root_names))
2361}
2362
2363#[derive(Debug, thiserror::Error)]
2364pub enum ChangeTravelError {
2365 #[error("Target id not found {0:?}")]
2366 TargetIdNotFound(ID),
2367 #[error("The shallow history of the doc doesn't include the target version")]
2368 TargetVersionNotIncluded,
2369}
2370
2371impl LoroDoc {
2372 pub fn travel_change_ancestors(
2373 &self,
2374 ids: &[ID],
2375 f: &mut dyn FnMut(ChangeMeta) -> ControlFlow<()>,
2376 ) -> Result<(), ChangeTravelError> {
2377 let (options, guard) = self.implicit_commit_then_stop();
2378 drop(guard);
2379 struct PendingNode(ChangeMeta);
2380 impl PartialEq for PendingNode {
2381 fn eq(&self, other: &Self) -> bool {
2382 self.0.lamport_last() == other.0.lamport_last() && self.0.id.peer == other.0.id.peer
2383 }
2384 }
2385
2386 impl Eq for PendingNode {}
2387 impl PartialOrd for PendingNode {
2388 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
2389 Some(self.cmp(other))
2390 }
2391 }
2392
2393 impl Ord for PendingNode {
2394 fn cmp(&self, other: &Self) -> Ordering {
2395 self.0
2396 .lamport_last()
2397 .cmp(&other.0.lamport_last())
2398 .then_with(|| self.0.id.peer.cmp(&other.0.id.peer))
2399 }
2400 }
2401
2402 for id in ids {
2403 let op_log = &self.oplog().lock();
2404 if !op_log.vv().includes_id(*id) {
2405 return Err(ChangeTravelError::TargetIdNotFound(*id));
2406 }
2407 if op_log.dag.shallow_since_vv().includes_id(*id) {
2408 return Err(ChangeTravelError::TargetVersionNotIncluded);
2409 }
2410 }
2411
2412 let mut visited = FxHashSet::default();
2413 let mut pending: BinaryHeap<PendingNode> = BinaryHeap::new();
2414 for id in ids {
2415 pending.push(PendingNode(ChangeMeta::from_change(
2416 &self.oplog().lock().get_change_at(*id).unwrap(),
2417 )));
2418 }
2419 while let Some(PendingNode(node)) = pending.pop() {
2420 let deps = node.deps.clone();
2421 if f(node).is_break() {
2422 break;
2423 }
2424
2425 for dep in deps.iter() {
2426 let Some(dep_node) = self.oplog().lock().get_change_at(dep) else {
2427 continue;
2428 };
2429 if visited.contains(&dep_node.id) {
2430 continue;
2431 }
2432
2433 visited.insert(dep_node.id);
2434 pending.push(PendingNode(ChangeMeta::from_change(&dep_node)));
2435 }
2436 }
2437
2438 let ans = Ok(());
2439 self.renew_txn_if_auto_commit(options);
2440 ans
2441 }
2442
2443 pub fn get_changed_containers_in(&self, id: ID, len: usize) -> FxHashSet<ContainerID> {
2444 self.with_barrier(|| {
2445 let mut set = FxHashSet::default();
2446 let len = i64::try_from(len).unwrap_or(i64::MAX);
2447 let start = i64::from(id.counter);
2448 let end = start.saturating_add(len);
2449 if end <= 0 {
2450 return set;
2451 }
2452
2453 let start = start.max(0).min(i64::from(i32::MAX));
2454 let end = end.max(0).min(i64::from(i32::MAX));
2455 if start >= end {
2456 return set;
2457 }
2458
2459 {
2460 let oplog = self.oplog().lock();
2461 let span = IdSpan::new(id.peer, start as i32, end as i32);
2462 for op in oplog.iter_ops(span) {
2463 let id = oplog.arena.get_container_id(op.container()).unwrap();
2464 set.insert(id);
2465 }
2466 }
2467 set
2468 })
2469 }
2470
2471 pub fn delete_root_container(&self, cid: ContainerID) {
2472 if !cid.is_root() {
2473 return;
2474 }
2475
2476 if !self.has_container(&cid) {
2478 return;
2479 }
2480
2481 let Some(h) = self.get_handler(cid.clone()) else {
2482 return;
2483 };
2484
2485 self.config
2486 .deleted_root_containers
2487 .lock()
2488 .insert(cid.clone());
2489 if let Err(e) = h.clear() {
2490 self.config.deleted_root_containers.lock().remove(&cid);
2491 eprintln!("Failed to clear handler: {:?}", e);
2492 }
2493 }
2494
2495 pub fn set_hide_empty_root_containers(&self, hide: bool) {
2496 self.config
2497 .hide_empty_root_containers
2498 .store(hide, std::sync::atomic::Ordering::Relaxed);
2499 }
2500}
2501
2502fn find_last_delete_op(oplog: &OpLog, id: ID, idx: ContainerIdx) -> Option<ID> {
2503 let start_vv = oplog
2514 .dag
2515 .frontiers_to_vv(&id.into())
2516 .unwrap_or_else(|| oplog.shallow_since_vv().to_vv());
2517
2518 let mut best: Option<((loro_common::Lamport, loro_common::PeerID), ID)> = None;
2522
2523 for change in oplog.iter_changes_peer_by_peer(&start_vv, oplog.vv()) {
2524 let peer = change.peer();
2525 for op in change.ops.iter() {
2526 if op.container != idx {
2527 continue;
2528 }
2529 if let InnerContent::List(InnerListOp::Delete(d)) = &op.content {
2530 if d.id_start.to_span(d.atom_len()).contains(id) {
2531 debug_assert!(op.counter >= change.id().counter);
2532 let op_lamport =
2533 change.lamport + (op.counter - change.id().counter) as loro_common::Lamport;
2534 let key = (op_lamport, peer);
2535 if best.is_none_or(|(bk, _)| key > bk) {
2536 best = Some((key, ID::new(peer, op.counter)));
2537 }
2538 }
2539 }
2540 }
2541 }
2542
2543 best.map(|(_, op_id)| op_id)
2544}
2545
2546struct BatchImportGuard<'a> {
2558 doc: &'a LoroDoc,
2559 was_attached: bool,
2562 txn: Option<LoroMutexGuard<'a, Option<Transaction>>>,
2565 options: Option<CommitOptions>,
2566 #[cfg(debug_assertions)]
2567 finished: bool,
2568}
2569
2570impl BatchImportGuard<'_> {
2571 fn finish(&mut self) -> LoroResult<VersionRange> {
2579 #[cfg(debug_assertions)]
2580 {
2581 self.finished = true;
2582 }
2583 let doc = self.doc;
2584 let txn = self.txn.take().expect("finish called twice");
2585 let options = self.options.take();
2586
2587 let pending = {
2588 let mut oplog = doc.oplog.lock();
2589 oplog.batch_importing = false;
2590 oplog.pending_changes.version_range()
2591 };
2592
2593 let mut checkout = Ok(());
2598 if self.was_attached {
2599 checkout = doc._checkout_to_latest_without_commit(true);
2600 if let Err(e) = &checkout {
2601 tracing::warn!("import_batch cannot reattach, rolling the batch back: {e}");
2605 doc.oplog.lock().rollback_import();
2606 *doc.diff_calculator.lock() = DiffCalculator::new(true);
2609 doc.set_detached(false);
2610 } else {
2611 doc.oplog.lock().commit_import_rollback();
2612 }
2613 }
2614
2615 doc._renew_txn_if_auto_commit_with_guard(options, txn);
2616 checkout.map(|()| pending)
2617 }
2618}
2619
2620#[cfg(debug_assertions)]
2623impl Drop for BatchImportGuard<'_> {
2624 fn drop(&mut self) {
2625 if !std::thread::panicking() {
2626 assert!(
2627 self.finished,
2628 "BatchImportGuard dropped without calling finish()"
2629 );
2630 }
2631 }
2632}
2633
2634#[derive(Debug)]
2635pub struct CommitWhenDrop<'a> {
2636 doc: &'a LoroDoc,
2637 default_options: CommitOptions,
2638}
2639
2640impl Drop for CommitWhenDrop<'_> {
2641 fn drop(&mut self) {
2642 {
2643 let mut guard = self.doc.txn.lock();
2644 if let Some(txn) = guard.as_mut() {
2645 txn.set_default_options(std::mem::take(&mut self.default_options));
2646 };
2647 }
2648
2649 self.doc.commit_then_renew();
2650 }
2651}
2652
2653#[derive(Debug, Clone)]
2655pub struct CommitOptions {
2656 pub origin: Option<InternalString>,
2659
2660 pub immediate_renew: bool,
2663
2664 pub timestamp: Option<Timestamp>,
2667
2668 pub commit_msg: Option<Arc<str>>,
2670}
2671
2672impl CommitOptions {
2673 pub fn new() -> Self {
2675 Self {
2676 origin: None,
2677 immediate_renew: true,
2678 timestamp: None,
2679 commit_msg: None,
2680 }
2681 }
2682
2683 pub fn origin(mut self, origin: &str) -> Self {
2685 self.origin = Some(origin.into());
2686 self
2687 }
2688
2689 pub fn immediate_renew(mut self, immediate_renew: bool) -> Self {
2691 self.immediate_renew = immediate_renew;
2692 self
2693 }
2694
2695 pub fn timestamp(mut self, timestamp: Timestamp) -> Self {
2699 self.timestamp = Some(timestamp);
2700 self
2701 }
2702
2703 pub fn commit_msg(mut self, commit_msg: &str) -> Self {
2705 self.commit_msg = Some(commit_msg.into());
2706 self
2707 }
2708
2709 pub fn set_origin(&mut self, origin: Option<&str>) {
2711 self.origin = origin.map(|x| x.into())
2712 }
2713
2714 pub fn set_timestamp(&mut self, timestamp: Option<Timestamp>) {
2716 self.timestamp = timestamp;
2717 }
2718}
2719
2720impl Default for CommitOptions {
2721 fn default() -> Self {
2722 Self::new()
2723 }
2724}
2725
2726#[cfg(test)]
2727mod test {
2728 use std::{
2729 panic::AssertUnwindSafe,
2730 sync::{
2731 atomic::{AtomicUsize, Ordering},
2732 Arc,
2733 },
2734 };
2735
2736 use crate::{
2737 cursor::PosType,
2738 encoding::json_schema::json::{JsonOpContent, JsonSchema, ListOp},
2739 encoding::{fast_snapshot::EMPTY_MARK, EncodeMode},
2740 loro::ExportMode,
2741 version::{Frontiers, VersionVector},
2742 LoroDoc, ToJson, TreeParentId,
2743 };
2744 use bytes::{BufMut, Bytes};
2745 use loro_common::{ContainerID, ContainerType, ID};
2746 use loro_kv_store::{mem_store::MemKvConfig, MemKvStore};
2747
2748 const XXH_SEED: u32 = u32::from_le_bytes(*b"LORO");
2749
2750 fn encode_import_blob(mode: EncodeMode, body: &[u8]) -> Vec<u8> {
2751 let mut ans = Vec::new();
2752 ans.extend_from_slice(b"loro");
2753 ans.extend_from_slice(&[0; 16]);
2754 ans.extend_from_slice(&mode.to_bytes());
2755 ans.extend_from_slice(body);
2756 let checksum = xxhash_rust::xxh32::xxh32(&ans[20..], XXH_SEED);
2757 ans[16..20].copy_from_slice(&checksum.to_le_bytes());
2758 ans
2759 }
2760
2761 fn encode_fast_snapshot_import(oplog_bytes: &[u8]) -> Vec<u8> {
2762 let mut body = Vec::new();
2763 body.put_u32_le(oplog_bytes.len() as u32);
2764 body.extend_from_slice(oplog_bytes);
2765 body.put_u32_le(EMPTY_MARK.len() as u32);
2766 body.extend_from_slice(EMPTY_MARK);
2767 body.put_u32_le(0);
2768 encode_import_blob(EncodeMode::FastSnapshot, &body)
2769 }
2770
2771 fn sstable_with_huge_meta_block_count() -> Vec<u8> {
2772 let mut bytes = Vec::new();
2773 bytes.extend_from_slice(b"LORO");
2774 bytes.push(0);
2775 bytes.put_u32_le(10_000_000);
2776 bytes.put_u32_le(xxhash_rust::xxh32::xxh32(&[], XXH_SEED));
2777 bytes.put_u32_le(5);
2778 bytes
2779 }
2780
2781 fn snapshot_oplog_with_malformed_block() -> Vec<u8> {
2782 let peer = 1;
2783 let id = ID::new(peer, 0);
2784 let vv = VersionVector::from_iter([(peer, 1)]);
2785 let frontiers = Frontiers::from_id(id);
2786 let mut store = MemKvStore::new(MemKvConfig::default());
2787 store.set(b"vv", vv.encode().into());
2788 store.set(b"fr", frontiers.encode().into());
2789 store.set(&id.to_bytes(), Bytes::from_static(&[0]));
2790 store.export_all().to_vec()
2791 }
2792
2793 fn make_json_import_stress_doc(peer: u64) -> LoroDoc {
2794 let doc = LoroDoc::new_auto_commit();
2795 doc.set_peer_id(peer).unwrap();
2796
2797 let text = doc.get_text("text");
2798 let mut text_pos = 0;
2799 for i in 0..32 {
2800 let chunk = format!("segment-{i}-abcdefghijklmnopqrstuvwxyz;");
2801 text.insert_unicode(text_pos, &chunk).unwrap();
2802 text_pos += chunk.chars().count();
2803 }
2804
2805 let list = doc.get_list("list");
2806 for i in 0..32 {
2807 list.insert(i, format!("item-{i}")).unwrap();
2808 }
2809
2810 let map = doc.get_map("map");
2811 for i in 0..32 {
2812 let key = format!("key-{i}");
2813 map.insert(&key, format!("value-{i}")).unwrap();
2814 }
2815
2816 let tree = doc.get_tree("tree");
2817 let mut parent = TreeParentId::Root;
2818 for i in 0..16 {
2819 let node = tree.create(parent).unwrap();
2820 let meta = tree.get_meta(node).unwrap();
2821 meta.insert("name", format!("node-{i}")).unwrap();
2822 meta.insert("payload", format!("payload-{i}-{}", "x".repeat(16)))
2823 .unwrap();
2824 parent = TreeParentId::Node(node);
2825 }
2826
2827 doc
2828 }
2829
2830 fn make_json_list_update_with_four_ops(peer: u64) -> (LoroDoc, JsonSchema) {
2831 let doc = LoroDoc::new();
2832 doc.set_peer_id(peer).unwrap();
2833 let map = doc.get_map("map");
2834 let list = doc.get_list("list");
2835 let text = doc.get_text("text");
2836
2837 let mut txn = doc.txn().unwrap();
2838 map.insert_with_txn(&mut txn, "prefix", "map-value".into())
2839 .unwrap();
2840 list.insert_with_txn(&mut txn, 0, "seed".into()).unwrap();
2841 text.insert_with_txn(&mut txn, 0, "text-value", PosType::Unicode)
2842 .unwrap();
2843 list.insert_with_txn(&mut txn, 1, "tail".into()).unwrap();
2844 txn.commit().unwrap();
2845
2846 let json = doc.export_json_updates(&Default::default(), &doc.oplog_vv(), false);
2847 assert_eq!(json.changes.len(), 1);
2848 assert_eq!(json.changes[0].ops.len(), 4);
2849 (doc, json)
2850 }
2851
2852 fn move_last_list_insert_far_out_of_bounds(json: &mut JsonSchema) {
2853 let last_change = json.changes.last_mut().unwrap();
2854 let last_op = last_change.ops.last_mut().unwrap();
2855 match &mut last_op.content {
2856 JsonOpContent::List(ListOp::Insert { pos, .. }) => {
2857 *pos = 1_000;
2858 }
2859 other => panic!("expected list insert op, got {other:?}"),
2860 }
2861 }
2862
2863 #[test]
2864 fn test_sync() {
2865 fn is_send_sync<T: Send + Sync>(_v: T) {}
2866 let loro = super::LoroDoc::new();
2867 is_send_sync(loro)
2868 }
2869
2870 #[test]
2871 fn import_rejects_huge_sstable_meta_block_count_without_panic() {
2872 let bytes = encode_fast_snapshot_import(&sstable_with_huge_meta_block_count());
2873
2874 let result = std::panic::catch_unwind(AssertUnwindSafe(|| LoroDoc::new().import(&bytes)));
2875 assert!(result.is_ok(), "malformed import should not panic");
2876 assert!(result.unwrap().is_err());
2877 }
2878
2879 #[test]
2880 fn import_rejects_malformed_change_block_without_panic() {
2881 let bytes = encode_fast_snapshot_import(&snapshot_oplog_with_malformed_block());
2882
2883 let result = std::panic::catch_unwind(AssertUnwindSafe(|| LoroDoc::new().import(&bytes)));
2884 assert!(result.is_ok(), "malformed import should not panic");
2885 assert!(result.unwrap().is_err());
2886 }
2887
2888 #[test]
2889 fn failed_import_rolls_back_oplog_and_arena() {
2890 let src = LoroDoc::new();
2891 src.set_peer_id(1).unwrap();
2892 let text = src.get_text("text");
2893 let mut txn = src.txn().unwrap();
2894 text.insert_with_txn(&mut txn, 0, "hello", PosType::Unicode)
2895 .unwrap();
2896 txn.commit().unwrap();
2897 let update = src.export(ExportMode::all_updates()).unwrap();
2898
2899 let dst = LoroDoc::new();
2900 let vv_before_import = dst.oplog_vv();
2901 let state_before_import = dst.get_deep_value();
2902 let err = dst
2903 .import_with(&update, "__loro_fail_import_state_apply".into())
2904 .unwrap_err();
2905 assert!(err.to_string().contains("state apply failpoint"));
2906 assert_eq!(dst.oplog_vv(), vv_before_import);
2907 assert_eq!(dst.get_deep_value(), state_before_import);
2908 assert!(dst.oplog().lock().is_empty());
2909
2910 dst.import(&update).unwrap();
2911 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2912 }
2913
2914 #[test]
2915 fn failed_incremental_import_restores_previous_change_store_block() {
2916 let src = LoroDoc::new();
2917 src.set_peer_id(1).unwrap();
2918 let text = src.get_text("text");
2919 let mut txn = src.txn().unwrap();
2920 text.insert_with_txn(&mut txn, 0, "a", PosType::Unicode)
2921 .unwrap();
2922 txn.commit().unwrap();
2923 let first_update = src.export(ExportMode::all_updates()).unwrap();
2924 let first_vv = src.oplog_vv();
2925
2926 let mut txn = src.txn().unwrap();
2927 text.insert_with_txn(&mut txn, 1, "b", PosType::Unicode)
2928 .unwrap();
2929 txn.commit().unwrap();
2930 let second_update = src.export(ExportMode::updates(&first_vv)).unwrap();
2931
2932 let dst = LoroDoc::new();
2933 dst.import(&first_update).unwrap();
2934 let vv_before_import = dst.oplog_vv();
2935 let state_before_import = dst.get_deep_value();
2936 dst.import_with(&second_update, "__loro_fail_import_state_apply".into())
2937 .unwrap_err();
2938 assert_eq!(dst.oplog_vv(), vv_before_import);
2939 assert_eq!(dst.get_deep_value(), state_before_import);
2940
2941 dst.import(&second_update).unwrap();
2942 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2943 }
2944
2945 #[test]
2946 fn failed_import_json_updates_rolls_back_complex_empty_doc() {
2947 let src = make_json_import_stress_doc(11);
2948 let json = src.export_json_updates(&Default::default(), &src.oplog_vv(), false);
2949
2950 let dst = LoroDoc::new();
2951 let vv_before_import = dst.oplog_vv();
2952 let frontiers_before_import = dst.oplog_frontiers();
2953 let state_before_import = dst.get_deep_value();
2954 for _ in 0..3 {
2955 crate::state::fail_next_import_state_apply_for_test();
2956 let err = dst.import_json_updates(json.clone()).unwrap_err();
2957 assert!(err.to_string().contains("state apply failpoint"));
2958 assert_eq!(dst.oplog_vv(), vv_before_import);
2959 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
2960 assert_eq!(dst.get_deep_value(), state_before_import);
2961 assert!(dst.oplog().lock().is_empty());
2962 }
2963
2964 dst.import_json_updates(json).unwrap();
2965 assert_eq!(dst.oplog_vv(), src.oplog_vv());
2966 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
2967 assert_eq!(dst.get_deep_value(), src.get_deep_value());
2968 }
2969
2970 #[test]
2971 fn failed_incremental_import_json_updates_restores_previous_change_store_block() {
2972 let src = LoroDoc::new_auto_commit();
2973 src.set_peer_id(12).unwrap();
2974 let text = src.get_text("text");
2975 text.insert_unicode(0, "a").unwrap();
2976 let list = src.get_list("list");
2977 list.push("seed").unwrap();
2978 let map = src.get_map("map");
2979 map.insert("seed", "value").unwrap();
2980 let tree = src.get_tree("tree");
2981 let root = tree.create(TreeParentId::Root).unwrap();
2982 tree.get_meta(root).unwrap().insert("name", "root").unwrap();
2983
2984 let first_vv = src.oplog_vv();
2985 let first_json = src.export_json_updates(&Default::default(), &first_vv, false);
2986
2987 let mut text_pos = text.len_unicode();
2988 for i in 0..64 {
2989 let chunk = format!("chunk-{i};");
2990 text.insert_unicode(text_pos, &chunk).unwrap();
2991 text_pos += chunk.chars().count();
2992 }
2993 for i in 0..32 {
2994 list.push(format!("after-{i}")).unwrap();
2995 let key = format!("after-{i}");
2996 map.insert(&key, format!("value-{i}")).unwrap();
2997 }
2998 let child = tree.create(TreeParentId::Node(root)).unwrap();
2999 tree.get_meta(child)
3000 .unwrap()
3001 .insert("name", "child")
3002 .unwrap();
3003
3004 let second_json = src.export_json_updates(&first_vv, &src.oplog_vv(), false);
3005
3006 let dst = LoroDoc::new();
3007 dst.import_json_updates(first_json).unwrap();
3008 let vv_before_import = dst.oplog_vv();
3009 let frontiers_before_import = dst.oplog_frontiers();
3010 let state_before_import = dst.get_deep_value();
3011
3012 for _ in 0..2 {
3013 crate::state::fail_next_import_state_apply_for_test();
3014 let err = dst.import_json_updates(second_json.clone()).unwrap_err();
3015 assert!(err.to_string().contains("state apply failpoint"));
3016 assert_eq!(dst.oplog_vv(), vv_before_import);
3017 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
3018 assert_eq!(dst.get_deep_value(), state_before_import);
3019 }
3020
3021 dst.import_json_updates(second_json).unwrap();
3022 assert_eq!(dst.oplog_vv(), src.oplog_vv());
3023 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
3024 assert_eq!(dst.get_deep_value(), src.get_deep_value());
3025 }
3026
3027 #[test]
3028 fn malformed_later_import_json_update_rolls_back_after_valid_prefix_enters_oplog() {
3029 let peer = 13;
3030 let (src, good_json) = make_json_list_update_with_four_ops(peer);
3031 let mut bad_json = good_json.clone();
3032 move_last_list_insert_far_out_of_bounds(&mut bad_json);
3033
3034 let good_dst = LoroDoc::new();
3035 good_dst.import_json_updates(good_json.clone()).unwrap();
3036 assert_eq!(good_dst.get_deep_value(), src.get_deep_value());
3037
3038 let last_op_counter = good_json.changes[0].ops.last().unwrap().counter;
3039 let prefix_vv = VersionVector::from_iter([(peer, last_op_counter)]);
3040 let prefix_json = src.export_json_updates(&Default::default(), &prefix_vv, false);
3041 assert_eq!(
3042 prefix_json.changes[0].ops.len(),
3043 good_json.changes[0].ops.len() - 1
3044 );
3045 let good_suffix_json = src.export_json_updates(&prefix_vv, &src.oplog_vv(), false);
3046 assert_eq!(good_suffix_json.changes[0].ops.len(), 1);
3047 let mut bad_suffix_json = good_suffix_json.clone();
3048 move_last_list_insert_far_out_of_bounds(&mut bad_suffix_json);
3049
3050 let prefix_dst = LoroDoc::new();
3051 prefix_dst.import_json_updates(prefix_json.clone()).unwrap();
3052 let vv_before_bad_suffix = prefix_dst.oplog_vv();
3053 let frontiers_before_bad_suffix = prefix_dst.oplog_frontiers();
3054 let state_before_bad_suffix = prefix_dst.get_deep_value();
3055
3056 let bad_suffix_json = serde_json::to_string(&bad_suffix_json).unwrap();
3057 let err = prefix_dst
3058 .import_json_updates(&bad_suffix_json)
3059 .unwrap_err();
3060 assert!(
3061 err.to_string().contains("list diff"),
3062 "expected state list bounds validation, got {err:?}"
3063 );
3064 assert_eq!(prefix_dst.oplog_vv(), vv_before_bad_suffix);
3065 assert_eq!(prefix_dst.oplog_frontiers(), frontiers_before_bad_suffix);
3066 assert_eq!(prefix_dst.get_deep_value(), state_before_bad_suffix);
3067
3068 prefix_dst.import_json_updates(good_suffix_json).unwrap();
3069 assert_eq!(prefix_dst.get_deep_value(), src.get_deep_value());
3070 assert_eq!(prefix_dst.oplog_vv(), src.oplog_vv());
3071
3072 let dst = LoroDoc::new();
3073 let vv_before_import = dst.oplog_vv();
3074 let frontiers_before_import = dst.oplog_frontiers();
3075 let state_before_import = dst.get_deep_value();
3076 let bad_json = serde_json::to_string(&bad_json).unwrap();
3077 let err = dst.import_json_updates(&bad_json).unwrap_err();
3078 assert!(
3079 err.to_string().contains("list diff"),
3080 "expected state list bounds validation, got {err:?}"
3081 );
3082 assert_eq!(dst.oplog_vv(), vv_before_import);
3083 assert_eq!(dst.oplog_frontiers(), frontiers_before_import);
3084 assert_eq!(dst.get_deep_value(), state_before_import);
3085 assert!(dst.oplog().lock().is_empty());
3086 }
3087
3088 #[test]
3089 fn failed_import_restores_pending_changes_that_were_applied_during_import() {
3090 let src = LoroDoc::new();
3091 src.set_peer_id(14).unwrap();
3092 let text = src.get_text("text");
3093
3094 let mut txn = src.txn().unwrap();
3095 text.insert_with_txn(&mut txn, 0, "a", PosType::Unicode)
3096 .unwrap();
3097 txn.commit().unwrap();
3098 let first_update = src.export(ExportMode::all_updates()).unwrap();
3099 let first_vv = src.oplog_vv();
3100
3101 let mut txn = src.txn().unwrap();
3102 text.insert_with_txn(&mut txn, 1, "b", PosType::Unicode)
3103 .unwrap();
3104 txn.commit().unwrap();
3105 let second_update = src.export(ExportMode::updates(&first_vv)).unwrap();
3106
3107 let dst = LoroDoc::new();
3108 let status = dst.import(&second_update).unwrap();
3109 assert!(status.success.is_empty());
3110 assert!(status.pending.is_some());
3111 let vv_before_dependency = dst.oplog_vv();
3112 let frontiers_before_dependency = dst.oplog_frontiers();
3113 let state_before_dependency = dst.get_deep_value();
3114
3115 crate::state::fail_next_import_state_apply_for_test();
3116 let err = dst.import(&first_update).unwrap_err();
3117 assert!(err.to_string().contains("state apply failpoint"));
3118 assert_eq!(dst.oplog_vv(), vv_before_dependency);
3119 assert_eq!(dst.oplog_frontiers(), frontiers_before_dependency);
3120 assert_eq!(dst.get_deep_value(), state_before_dependency);
3121
3122 dst.import(&first_update).unwrap();
3123 assert_eq!(dst.oplog_vv(), src.oplog_vv());
3124 assert_eq!(dst.oplog_frontiers(), src.oplog_frontiers());
3125 assert_eq!(dst.get_deep_value(), src.get_deep_value());
3126 }
3127
3128 #[test]
3129 fn failed_import_json_updates_does_not_emit_or_leave_events() {
3130 let (src, good_json) = make_json_list_update_with_four_ops(15);
3131 let mut bad_json = good_json.clone();
3132 move_last_list_insert_far_out_of_bounds(&mut bad_json);
3133
3134 let dst = LoroDoc::new();
3135 let event_count = Arc::new(AtomicUsize::new(0));
3136 let event_count_cloned = event_count.clone();
3137 let _sub = dst.subscribe_root(Arc::new(move |_| {
3138 event_count_cloned.fetch_add(1, Ordering::SeqCst);
3139 }));
3140
3141 let bad_json = serde_json::to_string(&bad_json).unwrap();
3142 let err = dst.import_json_updates(&bad_json).unwrap_err();
3143 assert!(
3144 err.to_string().contains("list diff"),
3145 "expected state list bounds validation, got {err:?}"
3146 );
3147 assert_eq!(event_count.load(Ordering::SeqCst), 0);
3148 assert!(dst.drop_pending_events().is_empty());
3149 assert!(dst.oplog().lock().is_empty());
3150
3151 dst.import_json_updates(good_json).unwrap();
3152 assert_eq!(event_count.load(Ordering::SeqCst), 1);
3153 assert_eq!(dst.get_deep_value(), src.get_deep_value());
3154 }
3155
3156 #[test]
3157 fn test_checkout() {
3158 let loro = LoroDoc::new();
3159 loro.set_peer_id(1).unwrap();
3160 let text = loro.get_text("text");
3161 let map = loro.get_map("map");
3162 let list = loro.get_list("list");
3163 let mut txn = loro.txn().unwrap();
3164 for i in 0..10 {
3165 map.insert_with_txn(&mut txn, "key", i.into()).unwrap();
3166 text.insert_with_txn(&mut txn, 0, &i.to_string(), PosType::Unicode)
3167 .unwrap();
3168 list.insert_with_txn(&mut txn, 0, i.into()).unwrap();
3169 }
3170 txn.commit().unwrap();
3171 let b = LoroDoc::new();
3172 b.import(&loro.export(ExportMode::Snapshot).unwrap())
3173 .unwrap();
3174 loro.checkout(&Frontiers::default()).unwrap();
3175 {
3176 let json = &loro.get_deep_value();
3177 assert_eq!(
3178 json.to_json_value(),
3179 serde_json::json!({"text":"","list":[],"map":{}})
3180 );
3181 }
3182
3183 b.checkout(&ID::new(1, 2).into()).unwrap();
3184 {
3185 let json = &b.get_deep_value();
3186 assert_eq!(
3187 json.to_json_value(),
3188 serde_json::json!({"text":"0","list":[0],"map":{"key":0}})
3189 );
3190 }
3191
3192 loro.checkout(&ID::new(1, 3).into()).unwrap();
3193 {
3194 let json = &loro.get_deep_value();
3195 assert_eq!(
3196 json.to_json_value(),
3197 serde_json::json!({"text":"0","list":[0],"map":{"key":1}})
3198 );
3199 }
3200
3201 b.checkout(&ID::new(1, 29).into()).unwrap();
3202 {
3203 let json = &b.get_deep_value();
3204 assert_eq!(
3205 json.to_json_value(),
3206 serde_json::json!({"text":"9876543210","list":[9,8,7,6,5,4,3,2,1,0],"map":{"key":9}})
3207 );
3208 }
3209 }
3210
3211 #[test]
3212 fn import_batch_err_181() {
3213 let a = LoroDoc::new_auto_commit();
3214 let update_a = a.export(ExportMode::Snapshot);
3215 let b = LoroDoc::new_auto_commit();
3216 b.import_batch(&[update_a.unwrap()]).unwrap();
3217 b.get_text("text")
3218 .insert(0, "hello", PosType::Unicode)
3219 .unwrap();
3220 b.commit_then_renew();
3221 let oplog = b.oplog().lock();
3222 drop(oplog);
3223 b.export(ExportMode::all_updates()).unwrap();
3224 }
3225
3226 #[test]
3227 fn poisoned_mutex_keeps_follow_up_operations_failed() {
3228 let doc = LoroDoc::new();
3229 let oplog = doc.oplog.clone();
3230 let _ = std::panic::catch_unwind(AssertUnwindSafe(|| {
3231 let _guard = oplog.lock();
3232 panic!("poison oplog");
3233 }));
3234
3235 let err = std::panic::catch_unwind(AssertUnwindSafe(|| doc.oplog_vv()))
3236 .expect_err("poisoned lock should continue to fail fast");
3237 let msg = if let Some(msg) = err.downcast_ref::<&str>() {
3238 (*msg).to_string()
3239 } else if let Some(msg) = err.downcast_ref::<String>() {
3240 msg.clone()
3241 } else {
3242 String::new()
3243 };
3244 assert!(msg.contains("poisoned LoroMutex"), "{msg}");
3245 }
3246
3247 #[test]
3248 fn repeated_independent_scalar_root_imports_scan_history_once() {
3249 let base = LoroDoc::new_auto_commit();
3250 base.set_peer_id(1).unwrap();
3251 for i in 0..64 {
3252 base.get_map("existing")
3253 .insert(&format!("key-{i}"), i)
3254 .unwrap();
3255 base.commit_then_renew();
3256 }
3257
3258 let target = LoroDoc::new();
3259 target
3260 .import(&base.export(ExportMode::Snapshot).unwrap())
3261 .unwrap();
3262 assert!(!target.has_history_cache());
3263
3264 for i in 0_u64..8 {
3265 let remote = LoroDoc::new_auto_commit();
3266 remote.set_peer_id(2 + i).unwrap();
3267 remote
3268 .get_map(format!("isolated-{i}"))
3269 .insert("value", i as i32)
3270 .unwrap();
3271 let update = remote.export(ExportMode::all_updates()).unwrap();
3272 target.import(&update).unwrap();
3273 assert_eq!(
3274 target.get_map(format!("isolated-{i}")).get("value"),
3275 Some((i as i32).into())
3276 );
3277 }
3278
3279 assert!(!target.has_history_cache());
3280 assert_eq!(
3281 target
3282 .oplog
3283 .lock()
3284 .change_store()
3285 .root_history_scan_count_for_test(),
3286 1
3287 );
3288 }
3289
3290 #[test]
3291 fn independent_fast_path_rejects_root_present_only_in_snapshot_state() {
3292 fn snapshot_sections(doc: &LoroDoc) -> crate::encoding::fast_snapshot::Snapshot {
3293 let (_, txn_guard) = doc.implicit_commit_then_stop();
3294 drop(txn_guard);
3295 crate::encoding::fast_snapshot::encode_snapshot_inner(doc).unwrap()
3296 }
3297
3298 let base = LoroDoc::new_auto_commit();
3299 base.set_peer_id(1).unwrap();
3300 base.get_map("existing").insert("value", "base").unwrap();
3301 let base_sections = snapshot_sections(&base);
3302
3303 let state_donor = LoroDoc::new_auto_commit();
3308 state_donor.set_peer_id(0).unwrap();
3309 state_donor
3310 .get_map("isolated")
3311 .insert("value", "stale")
3312 .unwrap();
3313 let donor_sections = snapshot_sections(&state_donor);
3314
3315 let target = LoroDoc::new();
3316 crate::encoding::fast_snapshot::decode_snapshot_inner(
3317 crate::encoding::fast_snapshot::Snapshot {
3318 oplog_bytes: base_sections.oplog_bytes,
3319 state_bytes: donor_sections.state_bytes,
3320 shallow_root_state_bytes: Bytes::new(),
3321 },
3322 &target,
3323 Default::default(),
3324 )
3325 .unwrap();
3326 assert!(!target.has_history_cache());
3327
3328 let left = LoroDoc::new_auto_commit();
3329 left.set_peer_id(2).unwrap();
3330 left.get_map("isolated").insert("value", "left").unwrap();
3331 let right = LoroDoc::new_auto_commit();
3332 right.set_peer_id(3).unwrap();
3333 right.get_map("isolated").insert("value", "winner").unwrap();
3334 let aggregate = LoroDoc::new();
3335 aggregate
3336 .import(&left.export(ExportMode::all_updates()).unwrap())
3337 .unwrap();
3338 aggregate
3339 .import(&right.export(ExportMode::all_updates()).unwrap())
3340 .unwrap();
3341
3342 let hits = super::isolated_fast_path_hits_for_test();
3343 target
3344 .import(&aggregate.export(ExportMode::all_updates()).unwrap())
3345 .unwrap();
3346
3347 assert_eq!(
3348 target.get_map("isolated").get("value"),
3349 Some("winner".into())
3350 );
3351 assert_eq!(super::isolated_fast_path_hits_for_test(), hits);
3352 }
3353
3354 #[test]
3355 fn independent_scalar_root_import_rolls_back_after_state_failure() {
3356 let base = LoroDoc::new_auto_commit();
3357 base.set_peer_id(1).unwrap();
3358 base.get_map("existing").insert("value", "base").unwrap();
3359
3360 let target = LoroDoc::new();
3361 target
3362 .import(&base.export(ExportMode::Snapshot).unwrap())
3363 .unwrap();
3364 let vv_before = target.oplog_vv();
3365 let state_before = target.get_deep_value();
3366
3367 let remote = LoroDoc::new_auto_commit();
3368 remote.set_peer_id(2).unwrap();
3369 remote.get_map("isolated").insert("value", 7).unwrap();
3370 let update = remote.export(ExportMode::all_updates()).unwrap();
3371
3372 crate::state::fail_next_import_state_apply_for_test();
3373 let err = target.import(&update).unwrap_err();
3374 assert!(err.to_string().contains("state apply failpoint"));
3375 assert_eq!(target.oplog_vv(), vv_before);
3376 assert_eq!(target.get_deep_value(), state_before);
3377 assert!(!target.has_history_cache());
3378
3379 target.import(&update).unwrap();
3380 assert_eq!(target.get_map("isolated").get("value"), Some(7.into()));
3381 }
3382
3383 #[test]
3384 fn independent_fast_path_rejects_root_name_seen_in_history() {
3385 let base = LoroDoc::new_auto_commit();
3386 base.set_peer_id(1).unwrap();
3387 base.get_map("shared").insert("value", "base").unwrap();
3388
3389 let target = LoroDoc::new();
3390 target
3391 .import(&base.export(ExportMode::Snapshot).unwrap())
3392 .unwrap();
3393 assert!(!target.has_history_cache());
3394
3395 let remote = LoroDoc::new_auto_commit();
3396 remote.set_peer_id(999).unwrap();
3397 remote.get_map("shared").insert("value", "remote").unwrap();
3398 let hits = super::isolated_fast_path_hits_for_test();
3399 target
3400 .import(&remote.export(ExportMode::all_updates()).unwrap())
3401 .unwrap();
3402
3403 assert_eq!(target.get_map("shared").get("value"), Some("remote".into()));
3404 assert_eq!(super::isolated_fast_path_hits_for_test(), hits);
3405 }
3406
3407 #[test]
3408 fn independent_fast_path_rejects_deleted_root_name() {
3409 let base = LoroDoc::new_auto_commit();
3410 base.set_peer_id(1).unwrap();
3411 let root_id = ContainerID::new_root("deleted", ContainerType::Map);
3412 base.get_map("deleted").insert("value", "base").unwrap();
3413 base.delete_root_container(root_id);
3414
3415 let target = LoroDoc::new();
3416 target
3417 .import(&base.export(ExportMode::Snapshot).unwrap())
3418 .unwrap();
3419 assert!(!target.has_history_cache());
3420
3421 let remote = LoroDoc::new_auto_commit();
3422 remote.set_peer_id(2).unwrap();
3423 remote.get_map("deleted").insert("value", "remote").unwrap();
3424 let hits = super::isolated_fast_path_hits_for_test();
3425 target
3426 .import(&remote.export(ExportMode::all_updates()).unwrap())
3427 .unwrap();
3428
3429 assert_eq!(super::isolated_fast_path_hits_for_test(), hits);
3430 let reference = LoroDoc::new();
3434 reference
3435 .import(&remote.export(ExportMode::all_updates()).unwrap())
3436 .unwrap();
3437 reference
3438 .import(&base.export(ExportMode::all_updates()).unwrap())
3439 .unwrap();
3440 use crate::handler::HandlerTrait;
3441 assert_eq!(
3442 target.get_map("deleted").get_deep_value(),
3443 reference.get_map("deleted").get_deep_value()
3444 );
3445 assert!(target.get_map("deleted").is_empty());
3446 }
3447}