From e5868cee71f2d3a58fa95e5d1777394a838d7347 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 2 Apr 2026 01:30:01 +0800 Subject: [PATCH 1/8] Fix stale DAG causal iteration after node splits --- crates/loro-internal/src/dag/iter.rs | 331 +++++++++++++++++++++------ 1 file changed, 263 insertions(+), 68 deletions(-) diff --git a/crates/loro-internal/src/dag/iter.rs b/crates/loro-internal/src/dag/iter.rs index 2f1322175..7b734d744 100644 --- a/crates/loro-internal/src/dag/iter.rs +++ b/crates/loro-internal/src/dag/iter.rs @@ -262,79 +262,274 @@ impl> Iterator for DagCausalIter<'_, D> { type Item = IterReturn; fn next(&mut self) -> Option { - if self.stack.is_empty() { - debug_assert_eq!( - 0, - self.target - .iter() - .map(|x| x.1.content_len() as i32) - .sum::() - ); - return None; - } - let node_id = self.stack.pop().unwrap(); - let target_span = self.target.get_mut(&node_id.peer).unwrap(); - debug_assert_eq!( - node_id.counter, - target_span.min(), - "{} {:?}", - node_id, - target_span - ); - - // // node_id may points into the middle of the node, we need to slice - let node = self.dag.get(node_id).unwrap(); - // node start_id may be smaller than node_id - let counter = node.id_span().counter; - let slice_from = if counter.start < target_span.start { - target_span.start - counter.start - } else { - 0 - }; - let slice_end = if counter.end < target_span.end { - counter.end - counter.start - } else { - target_span.end - counter.start - }; - assert!(slice_end > slice_from); - - let last_counter = node.id_last().counter; - target_span.set_start(last_counter + 1); - - // tracing::span!(tracing::Level::INFO, "Dag Causal"); - - // - - // NOTE: we expect user to update the tracker, to apply node, after visiting the node - self.frontier = Frontiers::from_id(node.id_start().inc(slice_end - 1)); - - let current_peer = node_id.peer; - let mut keys = Vec::new(); - let mut heap = BinaryHeap::new(); - // The in-degree of the successor node minus 1, and if it becomes 0, it is added to the heap - for (key, succ) in self.succ.range(node.id_start()..node.id_end()) { - keys.push(*key); - for succ_id in succ.iter() { - self.out_degrees.entry(*succ_id).and_modify(|i| *i -= 1); - if let Some(in_degree) = self.out_degrees.get(succ_id) { - if in_degree.is_zero() { - heap.push((succ_id.peer != current_peer, *succ_id)); - self.out_degrees.remove(succ_id); + loop { + if self.stack.is_empty() { + debug_assert_eq!( + 0, + self.target + .iter() + .map(|x| x.1.content_len() as i32) + .sum::() + ); + return None; + } + + let node_id = self.stack.pop().unwrap(); + let (node, slice_from, slice_end, next_same_peer) = { + let target_span = self.target.get_mut(&node_id.peer).unwrap(); + if node_id.counter != target_span.min() { + debug_assert!( + node_id.counter < target_span.min(), + "{} {:?}", + node_id, + target_span + ); + continue; + } + + // node_id may point into the middle of the node, we need to slice + let node = self.dag.get(node_id).unwrap(); + // node start_id may be smaller than node_id + let counter = node.id_span().counter; + let slice_from = if counter.start < target_span.start { + target_span.start - counter.start + } else { + 0 + }; + let slice_end = if counter.end < target_span.end { + counter.end - counter.start + } else { + target_span.end - counter.start + }; + + if slice_end <= slice_from { + debug_assert_eq!(slice_end, slice_from, "{node_id} {:?}", target_span); + continue; + } + + let consumed_last_counter = counter.start + slice_end - 1; + target_span.set_start(consumed_last_counter + 1); + let next_same_peer = if target_span.content_len() > 0 { + Some(ID::new(node_id.peer, target_span.min())) + } else { + None + }; + + (node, slice_from, slice_end, next_same_peer) + }; + + if let Some(next_id) = next_same_peer { + if !self.out_degrees.contains_key(&next_id) && !self.stack.contains(&next_id) { + self.stack.push(next_id); + } + } + + // NOTE: we expect user to update the tracker, to apply node, after visiting the node + self.frontier = Frontiers::from_id(node.id_start().inc(slice_end - 1)); + + let current_peer = node_id.peer; + let mut keys = Vec::new(); + let mut heap = BinaryHeap::new(); + // The in-degree of the successor node minus 1, and if it becomes 0, it is added to the heap + for (key, succ) in self.succ.range(node.id_start()..node.id_end()) { + keys.push(*key); + for succ_id in succ.iter() { + self.out_degrees.entry(*succ_id).and_modify(|i| *i -= 1); + if let Some(in_degree) = self.out_degrees.get(succ_id) { + if in_degree.is_zero() { + heap.push((succ_id.peer != current_peer, *succ_id)); + self.out_degrees.remove(succ_id); + } } } } + // Nodes that have been traversed are removed from the graph to avoid being covered by other node ranges again + keys.into_iter().for_each(|k| { + self.succ.remove(&k); + }); + while let Some(id) = heap.pop() { + self.stack.push(id.1) + } + + return Some(IterReturn { + data: node, + slice: slice_from..slice_end, + }); } - // Nodes that have been traversed are removed from the graph to avoid being covered by other node ranges again - keys.into_iter().for_each(|k| { - self.succ.remove(&k); - }); - while let Some(id) = heap.pop() { - self.stack.push(id.1) + } +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use super::*; + use crate::{ + change::Lamport, + id::ID, + span::{CounterSpan, HasId, HasLamport}, + version::VersionVector, + }; + use rle::{HasLength, Sliceable}; + + #[derive(Debug, Clone)] + struct DummyNode { + id: ID, + lamport: Lamport, + len: usize, + deps: Frontiers, + } + + impl DagNode for DummyNode { + fn deps(&self) -> &Frontiers { + &self.deps } + } + + impl Sliceable for DummyNode { + fn slice(&self, from: usize, to: usize) -> Self { + Self { + id: self.id.inc(from as i32), + lamport: self.lamport + from as Lamport, + len: to - from, + deps: if from > 0 { + self.id.inc(from as i32 - 1).into() + } else { + self.deps.clone() + }, + } + } + } + + impl HasLamport for DummyNode { + fn lamport(&self) -> Lamport { + self.lamport + } + } + + impl HasId for DummyNode { + fn id_start(&self) -> ID { + self.id + } + } + + impl HasLength for DummyNode { + fn content_len(&self) -> usize { + self.len + } + } + + #[derive(Debug)] + struct DummyDag { + nodes: BTreeMap, + vv: VersionVector, + frontier: Frontiers, + } + + impl Dag for DummyDag { + type Node = DummyNode; + + fn get(&self, id: ID) -> Option { + self.nodes + .range(..=id) + .next_back() + .filter(|(_, node)| node.contains_id(id)) + .map(|(_, node)| node.clone()) + } + + fn frontier(&self) -> &Frontiers { + &self.frontier + } + + fn vv(&self) -> &VersionVector { + &self.vv + } + + fn contains(&self, id: ID) -> bool { + self.nodes + .range(..=id) + .next_back() + .is_some_and(|(_, node)| node.contains_id(id)) + } + } + + #[test] + fn stale_iterator_state_repairs_missing_same_peer_continuation() { + let first = DummyNode { + id: ID::new(1, 0), + lamport: 0, + len: 5, + deps: Frontiers::default(), + }; + let second = DummyNode { + id: ID::new(1, 5), + lamport: 5, + len: 5, + deps: ID::new(1, 4).into(), + }; + let mut vv = VersionVector::default(); + vv.set_end(second.id_end()); + let dag = DummyDag { + frontier: second.id_last().into(), + nodes: [(first.id_start(), first), (second.id_start(), second)] + .into_iter() + .collect(), + vv, + }; + + let mut target = IdSpanVector::default(); + target.insert(1, CounterSpan::new(0, 7)); + let mut iter = DagCausalIter { + dag: &dag, + frontier: Frontiers::default(), + target, + out_degrees: Default::default(), + succ: BTreeMap::default(), + // This models the stale iterator state after `new()` saw one large + // node and queued only the first peer segment. + stack: vec![ID::new(1, 0)], + }; + + let first = iter.next().expect("first segment should be yielded"); + assert_eq!(first.data.id_start(), ID::new(1, 0)); + assert_eq!(first.slice, 0..5); + + let second = iter.next().expect("second segment should be re-queued"); + assert_eq!(second.data.id_start(), ID::new(1, 5)); + assert_eq!(second.slice, 0..2); + + assert!(iter.next().is_none()); + } + + #[test] + fn larger_node_only_advances_consumed_slice() { + let node = DummyNode { + id: ID::new(1, 0), + lamport: 0, + len: 10, + deps: Frontiers::default(), + }; + let mut vv = VersionVector::default(); + vv.set_end(node.id_end()); + let dag = DummyDag { + frontier: node.id_last().into(), + nodes: [(node.id_start(), node)].into_iter().collect(), + vv, + }; + + let mut target = IdSpanVector::default(); + target.insert(1, CounterSpan::new(0, 7)); + let mut iter = DagCausalIter { + dag: &dag, + frontier: Frontiers::default(), + target, + out_degrees: Default::default(), + succ: BTreeMap::default(), + stack: vec![ID::new(1, 0)], + }; - Some(IterReturn { - data: node, - slice: slice_from..slice_end, - }) + let item = iter.next().expect("target slice should be yielded"); + assert_eq!(item.slice, 0..7); + assert_eq!(iter.target.get(&1).unwrap().content_len(), 0); } } From d5628195d2a02da38431d83e3b2c31eb96ddd134 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 2 Apr 2026 01:38:06 +0800 Subject: [PATCH 2/8] Fix richtext cursor updates at fragment tails --- .../src/container/richtext/tracker.rs | 14 ++ .../richtext/tracker/id_to_cursor.rs | 219 +++++++++++++++++- 2 files changed, 228 insertions(+), 5 deletions(-) diff --git a/crates/loro-internal/src/container/richtext/tracker.rs b/crates/loro-internal/src/container/richtext/tracker.rs index 18eac0106..70cab4f18 100644 --- a/crates/loro-internal/src/container/richtext/tracker.rs +++ b/crates/loro-internal/src/container/richtext/tracker.rs @@ -671,6 +671,20 @@ mod test { assert_eq!(t.rope.len(), 10); } + #[test] + fn repeated_tail_splits_keep_id_to_cursor_consistent() { + let mut t = Tracker::new(); + t.insert(IdFull::new(1, 0, 0), 0, RichtextChunk::new_text(0..300)); + + for (i, pos) in [100, 201, 252, 278].into_iter().enumerate() { + let op_id = IdFull::new(2, i as Counter, i as Lamport); + let start = 1000 + i as u32; + t.insert(op_id, pos, RichtextChunk::new_text(start..start + 1)); + } + + t.check(); + } + #[test] fn test_checkout_in_doc_with_del_span() { let mut t = Tracker::new(); diff --git a/crates/loro-internal/src/container/richtext/tracker/id_to_cursor.rs b/crates/loro-internal/src/container/richtext/tracker/id_to_cursor.rs index 2c907f66e..d716069a3 100644 --- a/crates/loro-internal/src/container/richtext/tracker/id_to_cursor.rs +++ b/crates/loro-internal/src/container/richtext/tracker/id_to_cursor.rs @@ -689,6 +689,10 @@ mod insert_set { impl SmallInsertSet { fn update(&mut self, from: usize, to: usize, new_leaf: LeafIndex) { + if from == to { + return; + } + let mut cur_scan_index: usize = 0; let old_set = std::mem::take(&mut self.set); let mut new_set = SmallVec::with_capacity(old_set.len() + 2); @@ -861,14 +865,21 @@ mod insert_set { } fn update(&mut self, from: usize, to: usize, new_leaf: LeafIndex) { - let Some(from) = self.tree.query::(&from) else { + if from == to { return; - }; - let Some(to) = self.tree.query::(&to) else { + } + + let Some(from) = self.tree.query::(&from) else { return; }; - - self.tree.update(from.cursor..to.cursor, &mut |x| { + let to = self + .tree + .query::(&to) + .map(|x| x.cursor) + .or_else(|| self.tree.end_cursor()) + .unwrap(); + + self.tree.update(from.cursor..to, &mut |x| { x.leaf = new_leaf; None }); @@ -1004,10 +1015,208 @@ impl Mergeable for Cursor { #[cfg(test)] mod test { + use std::{any::Any, ops::Range, panic::AssertUnwindSafe}; + use super::*; + use generic_btree::{ + rle::{CanRemove, TryInsert}, + BTree, BTreeTrait, UseLengthFinder, + }; + use smallvec::smallvec; + + #[derive(Debug, Clone)] + struct DummyElem; + + impl generic_btree::rle::HasLength for DummyElem { + fn rle_len(&self) -> usize { + 1 + } + } + + impl generic_btree::rle::Sliceable for DummyElem { + fn _slice(&self, range: Range) -> Self { + assert_eq!(range.start, 0); + assert_eq!(range.end, 1); + DummyElem + } + + fn split(&mut self, _pos: usize) -> Self { + unreachable!() + } + } + + impl generic_btree::rle::Mergeable for DummyElem { + fn can_merge(&self, _rhs: &Self) -> bool { + false + } + + fn merge_right(&mut self, _rhs: &Self) { + unreachable!() + } + + fn merge_left(&mut self, _left: &Self) { + unreachable!() + } + } + + impl TryInsert for DummyElem { + fn try_insert(&mut self, _pos: usize, elem: Self) -> Result<(), Self> { + Err(elem) + } + } + + impl CanRemove for DummyElem { + fn can_remove(&self) -> bool { + false + } + } + + struct DummyTree; + + impl BTreeTrait for DummyTree { + type Elem = DummyElem; + type Cache = isize; + type CacheDiff = isize; + const USE_DIFF: bool = true; + + fn calc_cache_internal( + cache: &mut Self::Cache, + caches: &[generic_btree::Child], + ) -> Self::CacheDiff { + let new_cache = caches.iter().map(|child| child.cache).sum::(); + let diff = new_cache - *cache; + *cache = new_cache; + diff + } + + fn apply_cache_diff(cache: &mut Self::Cache, diff: &Self::CacheDiff) { + *cache += diff; + } + + fn merge_cache_diff(diff1: &mut Self::CacheDiff, diff2: &Self::CacheDiff) { + *diff1 += diff2; + } + + fn get_elem_cache(_elem: &Self::Elem) -> Self::Cache { + 1 + } + + fn new_cache_to_diff(cache: &Self::Cache) -> Self::CacheDiff { + *cache + } + + fn sub_cache(cache_lhs: &Self::Cache, cache_rhs: &Self::Cache) -> Self::CacheDiff { + cache_lhs - cache_rhs + } + } + + impl UseLengthFinder for DummyTree { + fn get_len(cache: &isize) -> usize { + *cache as usize + } + } + + fn dummy_leaf() -> LeafIndex { + let mut tree = BTree::::new(); + tree.push(DummyElem).leaf + } + + fn dummy_leaves(n: usize) -> Vec { + let mut tree = BTree::::new(); + (0..n).map(|_| tree.push(DummyElem).leaf).collect() + } + + fn panic_message(payload: Box) -> String { + if let Some(msg) = payload.downcast_ref::() { + msg.clone() + } else if let Some(msg) = payload.downcast_ref::<&str>() { + msg.to_string() + } else { + "".to_string() + } + } #[test] fn test_id_to_cursor() { let _map = IdToCursor::default(); } + + #[test] + fn zero_len_fragment_panics_with_left_0_right_3() { + let peer = 1; + let mut map = IdToCursor::default(); + map.map.insert( + peer, + vec![ + Fragment { + counter: 0, + cursor: Cursor::Insert(insert_set::InsertSet::Small( + insert_set::SmallInsertSet { + set: smallvec![], + len: 0, + }, + )), + }, + Fragment { + counter: 3, + cursor: Cursor::new_insert(dummy_leaf(), 1), + }, + ], + ); + + let err = std::panic::catch_unwind(AssertUnwindSafe(|| { + map.update_insert(IdSpan::new(peer, 0, 3), dummy_leaf()); + })) + .expect_err("expected zero-length fragment to trip the invariant"); + let msg = panic_message(err); + assert!(msg.contains("left == right"), "{msg}"); + assert!(msg.contains("left: 0"), "{msg}"); + assert!(msg.contains("right: 3"), "{msg}"); + } + + #[test] + fn zero_width_small_update_keeps_insert_set_non_empty() { + let leaf = dummy_leaf(); + let mut set = insert_set::InsertSet::new(leaf, 3); + set.update(0, 0, dummy_leaf()); + + match set { + insert_set::InsertSet::Small(set) => { + assert_eq!(set.len, 3); + assert_eq!(set.set.len(), 1); + assert_eq!(set.set[0].len, 3); + } + insert_set::InsertSet::Large(_) => panic!("expected small insert set"), + } + } + + #[test] + fn large_update_can_replace_the_tail_range() { + let leaves = dummy_leaves(4); + let mut set = insert_set::InsertSet::Small(insert_set::SmallInsertSet { + set: smallvec![ + Insert { + leaf: leaves[0], + len: 100, + }, + Insert { + leaf: leaves[1], + len: 100, + }, + Insert { + leaf: leaves[2], + len: 100, + } + ], + len: 300, + }); + set.update(0, 1, leaves[3]); + let new_leaf = leaves[3]; + set.update_many(&[(200, 300, new_leaf)]); + + assert_ne!(set.get_insert(199), Some(new_leaf)); + for i in 200..300 { + assert_eq!(set.get_insert(i), Some(new_leaf)); + } + } } From 2f10e43d938811822ea6e6246444e236331a73a7 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 2 Apr 2026 01:45:20 +0800 Subject: [PATCH 3/8] Add list import regression for split tracking --- crates/loro-internal/src/handler.rs | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index f342116c0..bec367e76 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -4347,17 +4347,44 @@ pub mod counter { #[cfg(test)] mod test { + use std::borrow::Cow; use super::{HandlerTrait, TextDelta}; + use crate::container::list::list_op::ListOp; use crate::cursor::PosType; use crate::loro::ExportMode; + use crate::op::ListSlice; use crate::state::TreeParentId; + use crate::txn::EventHint; use crate::version::Frontiers; use crate::LoroDoc; use crate::{fx_map, ToJson}; use loro_common::{LoroValue, ID}; use serde_json::json; + fn insert_many_with_single_list_op( + txn: &mut crate::txn::Transaction, + list: &crate::handler::ListHandler, + pos: usize, + values: Vec, + ) { + let len = values.len(); + let inner = list.inner.try_attached_state().unwrap(); + txn.apply_local_op( + inner.container_idx, + crate::op::RawOpContent::List(ListOp::Insert { + slice: ListSlice::RawData(Cow::Owned(values)), + pos, + }), + EventHint::InsertList { + len: len as u32, + pos, + }, + &inner.doc, + ) + .unwrap(); + } + #[test] fn richtext_handler() { let loro = LoroDoc::new(); @@ -4422,6 +4449,40 @@ mod test { } } + #[test] + fn list_import_batch_stays_consistent_after_repeated_tail_splits() { + let doc_a = LoroDoc::new(); + doc_a.set_peer_id(1).unwrap(); + let mut txn = doc_a.txn().unwrap(); + let list_a = txn.get_list("list"); + insert_many_with_single_list_op( + &mut txn, + &list_a, + 0, + (0..300).map(|i| LoroValue::I64(i)).collect(), + ); + txn.commit().unwrap(); + + let doc_b = LoroDoc::new(); + doc_b.set_peer_id(2).unwrap(); + doc_b.import(&doc_a.export(ExportMode::all_updates()).unwrap()) + .unwrap(); + + let list_b = doc_b.get_list("list"); + let mut vv = doc_a.oplog_vv(); + let mut updates = Vec::new(); + for (i, pos) in [100, 201, 252, 278].into_iter().enumerate() { + list_b.insert(pos, 1000 + i as i64).unwrap(); + updates.push(doc_b.export(ExportMode::updates(&vv)).unwrap()); + vv = doc_b.oplog_vv(); + } + + doc_a.import_batch(&updates).unwrap(); + doc_a.check_state_diff_calc_consistency_slow(); + doc_b.check_state_diff_calc_consistency_slow(); + assert_eq!(doc_a.get_deep_value(), doc_b.get_deep_value()); + } + #[test] fn richtext_handler_mark() { let loro = LoroDoc::new_auto_commit(); From caef6ec6fc4f474112c0f031d1fcc3f3d11dff23 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 2 Apr 2026 01:47:11 +0800 Subject: [PATCH 4/8] Recover document locks after panic poisoning --- crates/loro-internal/src/lock.rs | 21 +++++++++++++++++---- crates/loro-internal/src/loro.rs | 16 ++++++++++++++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/crates/loro-internal/src/lock.rs b/crates/loro-internal/src/lock.rs index aedd5962a..cf5c5f2a2 100644 --- a/crates/loro-internal/src/lock.rs +++ b/crates/loro-internal/src/lock.rs @@ -126,9 +126,6 @@ impl LoroMutex { /// released in the reverse acquisition order (LIFO). The callsite is /// recorded to improve panic diagnostics. /// - /// Errors: - /// - Propagates [`std::sync::PoisonError`] from the underlying mutex. - /// /// Panics: /// - If the current thread already holds a lock with kind `>= self.kind`. /// - If the guard is later dropped out of acquisition order. @@ -147,7 +144,9 @@ impl LoroMutex { ); } - let ans = self.lock.lock()?; + // A previous panic while holding the mutex should not permanently disable + // the document. Recover the inner value and let the caller decide what to do next. + let ans = self.lock.lock().unwrap_or_else(|e| e.into_inner()); *v.lock().unwrap_or_else(|e| e.into_inner()) = this; let ans = LoroMutexGuard { guard: ans, @@ -357,4 +356,18 @@ mod tests { // This line should be reported in the panic message let _guard2 = mutex2.lock().unwrap(); } + + #[test] + fn test_poisoned_lock_can_be_recovered() { + let group = LoroLockGroup::new(); + let mutex = group.new_lock(42, LockKind::Txn); + + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let _guard = mutex.lock().unwrap(); + panic!("poison the lock"); + })); + + let guard = mutex.lock().unwrap(); + assert_eq!(*guard, 42); + } } diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index 0ff194964..1f938cda2 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -2153,6 +2153,8 @@ impl Default for CommitOptions { #[cfg(test)] mod test { + use std::panic::AssertUnwindSafe; + use crate::{cursor::PosType, loro::ExportMode, version::Frontiers, LoroDoc, ToJson}; use loro_common::ID; @@ -2232,4 +2234,18 @@ mod test { drop(oplog); b.export(ExportMode::all_updates()).unwrap(); } + + #[test] + fn poisoned_mutex_allows_follow_up_operations() { + let doc = LoroDoc::new(); + let oplog = doc.oplog.clone(); + let _ = std::panic::catch_unwind(AssertUnwindSafe(|| { + let _guard = oplog.lock().unwrap(); + panic!("poison oplog"); + })); + + let vv = std::panic::catch_unwind(AssertUnwindSafe(|| doc.oplog_vv())) + .expect("poisoned lock should be recovered"); + assert!(vv.is_empty()); + } } From a28c207df26457a3f8891331f746024a85fa2a4e Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 2 Apr 2026 11:23:36 +0800 Subject: [PATCH 5/8] Add unpoisoned lock helpers across core internals --- crates/loro-internal/src/arena.rs | 118 +++++------ crates/loro-internal/src/awareness.rs | 19 +- crates/loro-internal/src/configure.rs | 7 +- crates/loro-internal/src/diff_calc/tree.rs | 5 +- .../src/encoding/fast_snapshot.rs | 12 +- .../src/encoding/shallow_snapshot.rs | 10 +- crates/loro-internal/src/handler.rs | 186 +++++++++--------- crates/loro-internal/src/handler/tree.rs | 45 ++--- crates/loro-internal/src/history_cache.rs | 9 +- .../src/jsonpath/jsonpath_impl.rs | 7 +- crates/loro-internal/src/lock.rs | 47 +++-- crates/loro-internal/src/loro.rs | 173 ++++++++-------- crates/loro-internal/src/oplog.rs | 7 +- .../loro-internal/src/oplog/change_store.rs | 67 +++---- crates/loro-internal/src/oplog/loro_dag.rs | 41 ++-- crates/loro-internal/src/pre_commit.rs | 9 +- crates/loro-internal/src/state.rs | 3 +- crates/loro-internal/src/state/analyzer.rs | 4 +- .../src/state/container_store.rs | 2 +- .../src/state/container_store/inner_store.rs | 3 +- crates/loro-internal/src/subscription.rs | 7 +- crates/loro-internal/src/sync.rs | 80 +++++++- crates/loro-internal/src/txn.rs | 26 +-- crates/loro-internal/src/undo.rs | 19 +- crates/loro-internal/src/utils/kv_wrapper.rs | 27 +-- .../loro-internal/src/utils/subscription.rs | 29 +-- 26 files changed, 532 insertions(+), 430 deletions(-) diff --git a/crates/loro-internal/src/arena.rs b/crates/loro-internal/src/arena.rs index b7ab1bf22..41b845967 100644 --- a/crates/loro-internal/src/arena.rs +++ b/crates/loro-internal/src/arena.rs @@ -1,5 +1,6 @@ mod str_arena; use self::str_arena::StrArena; +use crate::sync::MutexExt as _; use crate::sync::{Mutex, MutexGuard}; use crate::{ change::Lamport, @@ -141,17 +142,17 @@ impl SharedArena { Self { inner: Arc::new(InnerSharedArena { container_idx_to_id: Mutex::new( - self.inner.container_idx_to_id.lock().unwrap().clone(), + self.inner.container_idx_to_id.lock_unpoisoned().clone(), ), - depth: Mutex::new(self.inner.depth.lock().unwrap().clone()), + depth: Mutex::new(self.inner.depth.lock_unpoisoned().clone()), container_id_to_idx: Mutex::new( - self.inner.container_id_to_idx.lock().unwrap().clone(), + self.inner.container_id_to_idx.lock_unpoisoned().clone(), ), - parents: Mutex::new(self.inner.parents.lock().unwrap().clone()), - values: Mutex::new(self.inner.values.lock().unwrap().clone()), - root_c_idx: Mutex::new(self.inner.root_c_idx.lock().unwrap().clone()), + parents: Mutex::new(self.inner.parents.lock_unpoisoned().clone()), + values: Mutex::new(self.inner.values.lock_unpoisoned().clone()), + root_c_idx: Mutex::new(self.inner.root_c_idx.lock_unpoisoned().clone()), str: self.inner.str.clone(), - parent_resolver: Mutex::new(self.inner.parent_resolver.lock().unwrap().clone()), + parent_resolver: Mutex::new(self.inner.parent_resolver.lock_unpoisoned().clone()), }), } } @@ -163,38 +164,38 @@ impl SharedArena { fn get_arena_guards(&self) -> ArenaGuards<'_> { ArenaGuards { - container_id_to_idx: self.inner.container_id_to_idx.lock().unwrap(), - container_idx_to_id: self.inner.container_idx_to_id.lock().unwrap(), - depth: self.inner.depth.lock().unwrap(), - parents: self.inner.parents.lock().unwrap(), - root_c_idx: self.inner.root_c_idx.lock().unwrap(), - parent_resolver: self.inner.parent_resolver.lock().unwrap(), + container_id_to_idx: self.inner.container_id_to_idx.lock_unpoisoned(), + container_idx_to_id: self.inner.container_idx_to_id.lock_unpoisoned(), + depth: self.inner.depth.lock_unpoisoned(), + parents: self.inner.parents.lock_unpoisoned(), + root_c_idx: self.inner.root_c_idx.lock_unpoisoned(), + parent_resolver: self.inner.parent_resolver.lock_unpoisoned(), } } pub fn register_container(&self, id: &ContainerID) -> ContainerIdx { - let mut container_id_to_idx = self.inner.container_id_to_idx.lock().unwrap(); + let mut container_id_to_idx = self.inner.container_id_to_idx.lock_unpoisoned(); if let Some(&idx) = container_id_to_idx.get(id) { return idx; } - let mut container_idx_to_id = self.inner.container_idx_to_id.lock().unwrap(); + let mut container_idx_to_id = self.inner.container_idx_to_id.lock_unpoisoned(); let idx = container_idx_to_id.len(); container_idx_to_id.push(id.clone()); let idx = ContainerIdx::from_index_and_type(idx as u32, id.container_type()); container_id_to_idx.insert(id.clone(), idx); if id.is_root() { - self.inner.root_c_idx.lock().unwrap().push(idx); - self.inner.parents.lock().unwrap().insert(idx, None); - self.inner.depth.lock().unwrap().push(NonZeroU16::new(1)); + self.inner.root_c_idx.lock_unpoisoned().push(idx); + self.inner.parents.lock_unpoisoned().insert(idx, None); + self.inner.depth.lock_unpoisoned().push(NonZeroU16::new(1)); } else { - self.inner.depth.lock().unwrap().push(None); + self.inner.depth.lock_unpoisoned().push(None); } idx } pub fn get_container_id(&self, idx: ContainerIdx) -> Option { - let lock = self.inner.container_idx_to_id.lock().unwrap(); + let lock = self.inner.container_idx_to_id.lock_unpoisoned(); lock.get(idx.to_index() as usize).cloned() } @@ -218,64 +219,64 @@ impl SharedArena { #[inline] pub fn idx_to_id(&self, id: ContainerIdx) -> Option { - let lock = self.inner.container_idx_to_id.lock().unwrap(); + let lock = self.inner.container_idx_to_id.lock_unpoisoned(); lock.get(id.to_index() as usize).cloned() } #[inline] pub fn with_idx_to_id(&self, f: impl FnOnce(&Vec) -> R) -> R { - let lock = self.inner.container_idx_to_id.lock().unwrap(); + let lock = self.inner.container_idx_to_id.lock_unpoisoned(); f(&lock) } pub fn alloc_str(&self, str: &str) -> StrAllocResult { - let mut text_lock = self.inner.str.lock().unwrap(); + let mut text_lock = self.inner.str.lock_unpoisoned(); _alloc_str(&mut text_lock, str) } /// return slice and unicode index pub fn alloc_str_with_slice(&self, str: &str) -> (BytesSlice, StrAllocResult) { - let mut text_lock = self.inner.str.lock().unwrap(); + let mut text_lock = self.inner.str.lock_unpoisoned(); _alloc_str_with_slice(&mut text_lock, str) } /// alloc str without extra info pub fn alloc_str_fast(&self, bytes: &[u8]) { - let mut text_lock = self.inner.str.lock().unwrap(); + let mut text_lock = self.inner.str.lock_unpoisoned(); text_lock.alloc(std::str::from_utf8(bytes).unwrap()); } #[inline] pub fn utf16_len(&self) -> usize { - self.inner.str.lock().unwrap().len_utf16() + self.inner.str.lock_unpoisoned().len_utf16() } #[inline] pub fn alloc_value(&self, value: LoroValue) -> usize { - let mut values_lock = self.inner.values.lock().unwrap(); + let mut values_lock = self.inner.values.lock_unpoisoned(); _alloc_value(&mut values_lock, value) } #[inline] pub fn alloc_values(&self, values: impl Iterator) -> std::ops::Range { - let mut values_lock = self.inner.values.lock().unwrap(); + let mut values_lock = self.inner.values.lock_unpoisoned(); _alloc_values(&mut values_lock, values) } #[inline] pub fn set_parent(&self, child: ContainerIdx, parent: Option) { - let mut parents = self.inner.parents.lock().unwrap(); + let mut parents = self.inner.parents.lock_unpoisoned(); parents.insert(child, parent); - let mut depth = self.inner.depth.lock().unwrap(); - let mut root_c_idx = self.inner.root_c_idx.lock().unwrap(); + let mut depth = self.inner.depth.lock_unpoisoned(); + let mut root_c_idx = self.inner.root_c_idx.lock_unpoisoned(); match parent { Some(p) => { // Acquire the two maps as mutable guards so we can lazily register // unknown parents while computing depth. - let mut idx_to_id_guard = self.inner.container_idx_to_id.lock().unwrap(); - let mut id_to_idx_guard = self.inner.container_id_to_idx.lock().unwrap(); - let parent_resolver_guard = self.inner.parent_resolver.lock().unwrap(); + let mut idx_to_id_guard = self.inner.container_idx_to_id.lock_unpoisoned(); + let mut id_to_idx_guard = self.inner.container_id_to_idx.lock_unpoisoned(); + let parent_resolver_guard = self.inner.parent_resolver.lock_unpoisoned(); if let Some(d) = get_depth( p, &mut depth, @@ -298,7 +299,7 @@ impl SharedArena { pub fn log_hierarchy(&self) { if cfg!(debug_assertions) { - for (c, p) in self.inner.parents.lock().unwrap().iter() { + for (c, p) in self.inner.parents.lock_unpoisoned().iter() { tracing::info!( "container {:?} {:?} {:?}", c, @@ -337,12 +338,12 @@ impl SharedArena { } // Try fast path first - if let Some(p) = self.inner.parents.lock().unwrap().get(&child).copied() { + if let Some(p) = self.inner.parents.lock_unpoisoned().get(&child).copied() { return p; } // Fallback: try to resolve parent lazily via the resolver if provided. - let resolver = self.inner.parent_resolver.lock().unwrap().clone(); + let resolver = self.inner.parent_resolver.lock_unpoisoned().clone(); if let Some(resolver) = resolver { let child_id = self.get_container_id(child).unwrap(); if let Some(parent_id) = resolver(child_id.clone()) { @@ -370,17 +371,17 @@ impl SharedArena { #[inline] pub fn slice_by_unicode(&self, range: impl RangeBounds) -> BytesSlice { - self.inner.str.lock().unwrap().slice_by_unicode(range) + self.inner.str.lock_unpoisoned().slice_by_unicode(range) } #[inline] pub fn slice_by_utf8(&self, range: impl RangeBounds) -> BytesSlice { - self.inner.str.lock().unwrap().slice_bytes(range) + self.inner.str.lock_unpoisoned().slice_bytes(range) } #[inline] pub fn slice_str_by_unicode_range(&self, range: Range) -> String { - let mut s = self.inner.str.lock().unwrap(); + let mut s = self.inner.str.lock_unpoisoned(); let s: &mut StrArena = &mut s; let mut ans = String::with_capacity(range.len()); ans.push_str(s.slice_str_by_unicode(range)); @@ -389,17 +390,17 @@ impl SharedArena { #[inline] pub fn with_text_slice(&self, range: Range, mut f: impl FnMut(&str)) { - f(self.inner.str.lock().unwrap().slice_str_by_unicode(range)) + f(self.inner.str.lock_unpoisoned().slice_str_by_unicode(range)) } #[inline] pub fn get_value(&self, idx: usize) -> Option { - self.inner.values.lock().unwrap().get(idx).cloned() + self.inner.values.lock_unpoisoned().get(idx).cloned() } #[inline] pub fn get_values(&self, range: Range) -> Vec { - (self.inner.values.lock().unwrap()[range]).to_vec() + (self.inner.values.lock_unpoisoned()[range]).to_vec() } pub fn convert_single_op( @@ -415,7 +416,8 @@ impl SharedArena { } pub fn can_import_snapshot(&self) -> bool { - self.inner.str.lock().unwrap().is_empty() && self.inner.values.lock().unwrap().is_empty() + self.inner.str.lock_unpoisoned().is_empty() + && self.inner.values.lock_unpoisoned().is_empty() } fn inner_convert_op( @@ -540,12 +542,12 @@ impl SharedArena { #[inline] pub fn export_containers(&self) -> Vec { - self.inner.container_idx_to_id.lock().unwrap().clone() + self.inner.container_idx_to_id.lock_unpoisoned().clone() } pub fn export_parents(&self) -> Vec> { - let parents = self.inner.parents.lock().unwrap(); - let containers = self.inner.container_idx_to_id.lock().unwrap(); + let parents = self.inner.parents.lock_unpoisoned(); + let containers = self.inner.container_idx_to_id.lock_unpoisoned(); containers .iter() .enumerate() @@ -563,17 +565,17 @@ impl SharedArena { /// So we need the flag type here. #[inline] pub(crate) fn root_containers(&self, _f: LoadAllFlag) -> Vec { - self.inner.root_c_idx.lock().unwrap().clone() + self.inner.root_c_idx.lock_unpoisoned().clone() } // TODO: this can return a u16 directly now, since the depths are always valid pub(crate) fn get_depth(&self, container: ContainerIdx) -> Option { - let mut depth_guard = self.inner.depth.lock().unwrap(); - let mut parents_guard = self.inner.parents.lock().unwrap(); - let mut root_c_idx_guard = self.inner.root_c_idx.lock().unwrap(); - let resolver_guard = self.inner.parent_resolver.lock().unwrap(); - let mut idx_to_id_guard = self.inner.container_idx_to_id.lock().unwrap(); - let mut id_to_idx_guard = self.inner.container_id_to_idx.lock().unwrap(); + let mut depth_guard = self.inner.depth.lock_unpoisoned(); + let mut parents_guard = self.inner.parents.lock_unpoisoned(); + let mut root_c_idx_guard = self.inner.root_c_idx.lock_unpoisoned(); + let resolver_guard = self.inner.parent_resolver.lock_unpoisoned(); + let mut idx_to_id_guard = self.inner.container_idx_to_id.lock_unpoisoned(); + let mut id_to_idx_guard = self.inner.container_id_to_idx.lock_unpoisoned(); get_depth( container, &mut depth_guard, @@ -589,7 +591,7 @@ impl SharedArena { &self, range: Range, ) -> impl Iterator + '_ { - let values = self.inner.values.lock().unwrap(); + let values = self.inner.values.lock_unpoisoned(); range .into_iter() .map(move |i| values.get(i).unwrap().clone()) @@ -599,7 +601,7 @@ impl SharedArena { &self, root_index: &loro_common::InternalString, ) -> Option { - let inner = self.inner.container_id_to_idx.lock().unwrap(); + let inner = self.inner.container_id_to_idx.lock_unpoisoned(); for t in loro_common::ContainerType::ALL_TYPES.iter() { let cid = ContainerID::Root { name: root_index.clone(), @@ -614,7 +616,7 @@ impl SharedArena { #[allow(unused)] pub(crate) fn log_all_values(&self) { - let values = self.inner.values.lock().unwrap(); + let values = self.inner.values.lock_unpoisoned(); for (i, v) in values.iter().enumerate() { loro_common::debug!("value {} {:?}", i, v); } @@ -760,7 +762,7 @@ impl SharedArena { where F: Fn(ContainerID) -> Option + Send + Sync + 'static, { - let mut slot = self.inner.parent_resolver.lock().unwrap(); + let mut slot = self.inner.parent_resolver.lock_unpoisoned(); *slot = resolver.map(|f| Arc::new(f) as Arc); } } diff --git a/crates/loro-internal/src/awareness.rs b/crates/loro-internal/src/awareness.rs index f097b569d..7c25907c8 100644 --- a/crates/loro-internal/src/awareness.rs +++ b/crates/loro-internal/src/awareness.rs @@ -9,6 +9,7 @@ //! //! The legacy `Awareness` type remains for backward compatibility but is deprecated in //! favor of `EphemeralStore`. +use crate::sync::MutexExt as _; use std::sync::atomic::AtomicI64; use std::sync::{Arc, Mutex}; @@ -321,7 +322,7 @@ impl EphemeralStore { /// // Subscribe and collect first 3 updates, then auto-unsubscribe /// let sub = store.subscribe_local_updates(Box::new(move |bytes| { /// println!("Received {} bytes of ephemeral data", bytes.len()); - /// let mut count = count_clone.lock().unwrap(); + /// let mut count = count_clone.lock_unpoisoned(); /// *count += 1; /// *count < 3 // Auto-unsubscribe after 3 updates /// })); @@ -381,7 +382,7 @@ impl EphemeralStoreInner { pub fn encode(&self, key: &str) -> Vec { let mut peers_info = Vec::new(); let now = get_sys_timestamp() as Timestamp; - let states = self.states.lock().unwrap(); + let states = self.states.lock_unpoisoned(); if let Some(peer_state) = states.get(key) { if now - peer_state.timestamp > self.timeout.load(std::sync::atomic::Ordering::Relaxed) { @@ -401,7 +402,7 @@ impl EphemeralStoreInner { pub fn encode_all(&self) -> Vec { let mut peers_info = Vec::new(); let now = get_sys_timestamp() as Timestamp; - let states = self.states.lock().unwrap(); + let states = self.states.lock_unpoisoned(); for (key, peer_state) in states.iter() { if now - peer_state.timestamp > self.timeout.load(std::sync::atomic::Ordering::Relaxed) { @@ -428,7 +429,7 @@ impl EphemeralStoreInner { let mut removed_keys = Vec::new(); let now = get_sys_timestamp() as Timestamp; let timeout = self.timeout.load(std::sync::atomic::Ordering::Relaxed); - let mut states = self.states.lock().unwrap(); + let mut states = self.states.lock_unpoisoned(); for EncodedState { key, value: record, @@ -486,14 +487,14 @@ impl EphemeralStoreInner { } pub fn get(&self, key: &str) -> Option { - let states = self.states.lock().unwrap(); + let states = self.states.lock_unpoisoned(); states.get(key).and_then(|x| x.state.clone()) } pub fn remove_outdated(&self) { let now = get_sys_timestamp() as Timestamp; let mut removed = Vec::new(); - let mut states = self.states.lock().unwrap(); + let mut states = self.states.lock_unpoisoned(); states.retain(|key, state| { if now - state.timestamp > self.timeout.load(std::sync::atomic::Ordering::Relaxed) { if state.state.is_some() { @@ -519,7 +520,7 @@ impl EphemeralStoreInner { } pub fn get_all_states(&self) -> FxHashMap { - let states = self.states.lock().unwrap(); + let states = self.states.lock_unpoisoned(); states .iter() .filter(|(_, v)| v.state.is_some()) @@ -528,7 +529,7 @@ impl EphemeralStoreInner { } pub fn keys(&self) -> Vec { - let states = self.states.lock().unwrap(); + let states = self.states.lock_unpoisoned(); states .keys() .filter(|&k| states.get(k).unwrap().state.is_some()) @@ -566,7 +567,7 @@ impl EphemeralStoreInner { fn _set_local_state(&self, key: &str, value: Option) { let is_delete = value.is_none(); - let mut states = self.states.lock().unwrap(); + let mut states = self.states.lock_unpoisoned(); let old = states.insert( key.to_string(), State { diff --git a/crates/loro-internal/src/configure.rs b/crates/loro-internal/src/configure.rs index 148e32139..31dd67638 100644 --- a/crates/loro-internal/src/configure.rs +++ b/crates/loro-internal/src/configure.rs @@ -1,3 +1,4 @@ +use crate::sync::{MutexExt as _, RwLockExt as _}; use loro_common::ContainerID; use rustc_hash::FxHashSet; @@ -19,7 +20,7 @@ pub struct Configure { impl LoroDoc { pub(crate) fn set_config(&self, config: &Configure) { - self.config_text_style(config.text_style_config.read().unwrap().clone()); + self.config_text_style(config.text_style_config.read_unpoisoned().clone()); self.set_record_timestamp(config.record_timestamp()); self.set_change_merge_interval(config.merge_interval()); self.set_detached_editing(config.detached_editing()); @@ -43,7 +44,7 @@ impl Configure { pub fn fork(&self) -> Self { Self { text_style_config: Arc::new(RwLock::new( - self.text_style_config.read().unwrap().clone(), + self.text_style_config.read_unpoisoned().clone(), )), record_timestamp: Arc::new(AtomicBool::new( self.record_timestamp @@ -58,7 +59,7 @@ impl Configure { .load(std::sync::atomic::Ordering::Relaxed), )), deleted_root_containers: Arc::new(Mutex::new( - self.deleted_root_containers.lock().unwrap().clone(), + self.deleted_root_containers.lock_unpoisoned().clone(), )), hide_empty_root_containers: Arc::new(AtomicBool::new( self.hide_empty_root_containers diff --git a/crates/loro-internal/src/diff_calc/tree.rs b/crates/loro-internal/src/diff_calc/tree.rs index 6a71bda39..e853ae645 100644 --- a/crates/loro-internal/src/diff_calc/tree.rs +++ b/crates/loro-internal/src/diff_calc/tree.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeSet, sync::Arc}; +use crate::sync::MutexExt as _; use fractional_index::FractionalIndex; use itertools::Itertools; use loro_common::{ContainerID, IdFull, IdLp, Lamport, PeerID, TreeID, ID}; @@ -164,7 +165,7 @@ impl TreeDiffCalculator { oplog.with_history_cache(|h| { let mark = h.ensure_importing_caches_exist(); let tree_ops = h.get_tree(&self.container, mark).unwrap(); - let mut tree_cache = tree_ops.tree().lock().unwrap(); + let mut tree_cache = tree_ops.tree().lock_unpoisoned(); let s = format!("checkout current {:?} to {:?}", &tree_cache.current_vv, &to); let s = tracing::span!(tracing::Level::INFO, "checkout", s = s); let _e = s.enter(); @@ -231,7 +232,7 @@ impl TreeDiffCalculator { oplog.with_history_cache(|h| { let mark = h.ensure_importing_caches_exist(); let tree_ops = h.get_tree(&self.container, mark).unwrap(); - let mut tree_cache = tree_ops.tree().lock().unwrap(); + let mut tree_cache = tree_ops.tree().lock_unpoisoned(); let mut parent_to_children_cache = TreeParentToChildrenCache::init_from_tree_cache(&tree_cache); let s = tracing::span!(tracing::Level::INFO, "checkout_diff"); diff --git a/crates/loro-internal/src/encoding/fast_snapshot.rs b/crates/loro-internal/src/encoding/fast_snapshot.rs index a9c00cffb..009100a6b 100644 --- a/crates/loro-internal/src/encoding/fast_snapshot.rs +++ b/crates/loro-internal/src/encoding/fast_snapshot.rs @@ -247,8 +247,8 @@ pub(crate) fn encode_snapshot_inner(doc: &LoroDoc) -> Snapshot { assert!(doc.drop_pending_events().is_empty()); let old_state_frontiers = doc.state_frontiers(); let was_detached = doc.is_detached(); - let oplog = doc.oplog().lock().unwrap(); - let mut state = doc.app_state().lock().unwrap(); + let oplog = doc.oplog().lock_unpoisoned(); + let mut state = doc.app_state().lock_unpoisoned(); let is_gc = state.store.shallow_root_store().is_some(); if is_gc { // TODO: PERF: this can be optimized by reusing the bytes of gc store @@ -273,7 +273,7 @@ pub(crate) fn encode_snapshot_inner(doc: &LoroDoc) -> Snapshot { drop(oplog); doc._checkout_without_emitting(&latest, false, true) .unwrap(); - state = doc.app_state().lock().unwrap(); + state = doc.app_state().lock_unpoisoned(); } state.ensure_all_alive_containers(); let state_bytes = state.store.encode(); @@ -305,7 +305,7 @@ pub(crate) fn decode_oplog(oplog: &mut OpLog, bytes: &[u8]) -> Result(doc: &LoroDoc, vv: &VersionVector, w: &mut W) { - let oplog = doc.oplog().lock().unwrap(); + let oplog = doc.oplog().lock_unpoisoned(); oplog.export_blocks_from(vv, w); } @@ -348,7 +348,7 @@ pub(crate) fn decode_snapshot_blob_meta( }; let doc = LoroDoc::new(); - let mut oplog = doc.oplog.lock().unwrap(); + let mut oplog = doc.oplog.lock_unpoisoned(); oplog.decode_change_store(oplog_bytes.to_vec().into())?; let timestamp = oplog.get_greatest_timestamp(oplog.dag.frontiers()); let f = oplog.dag.shallow_since_frontiers().clone(); @@ -370,7 +370,7 @@ pub(crate) fn decode_updates_blob_meta( parsed: ParsedHeaderAndBody, ) -> LoroResult { let doc = LoroDoc::new(); - let mut oplog = doc.oplog.lock().unwrap(); + let mut oplog = doc.oplog.lock_unpoisoned(); let changes = decode_updates(&mut oplog, parsed.body.to_vec().into())?; let mut start_vv = VersionVector::new(); let mut end_vv = VersionVector::new(); diff --git a/crates/loro-internal/src/encoding/shallow_snapshot.rs b/crates/loro-internal/src/encoding/shallow_snapshot.rs index 5120fb657..f6fbb5eaf 100644 --- a/crates/loro-internal/src/encoding/shallow_snapshot.rs +++ b/crates/loro-internal/src/encoding/shallow_snapshot.rs @@ -33,7 +33,7 @@ pub(crate) fn export_shallow_snapshot_inner( doc: &LoroDoc, start_from: &Frontiers, ) -> Result<(Snapshot, Frontiers), LoroEncodeError> { - let oplog = doc.oplog().lock().unwrap(); + let oplog = doc.oplog().lock_unpoisoned(); let start_from = calc_shallow_doc_start(&oplog, start_from); let mut start_vv = oplog.dag().frontiers_to_vv(&start_from).unwrap(); for id in start_from.iter() { @@ -76,7 +76,7 @@ pub(crate) fn export_shallow_snapshot_inner( drop(oplog); doc._checkout_without_emitting(&start_from, false, false) .unwrap(); - let mut state = doc.app_state().lock().unwrap(); + let mut state = doc.app_state().lock_unpoisoned(); let alive_containers = state.ensure_all_alive_containers(); if has_unknown_container(alive_containers.iter()) { return Err(LoroEncodeError::UnknownContainer); @@ -89,7 +89,7 @@ pub(crate) fn export_shallow_snapshot_inner( doc._checkout_without_emitting(&latest_frontiers, false, false) .unwrap(); let state_bytes = if ops_num > MAX_OPS_NUM_TO_ENCODE_WITHOUT_LATEST_STATE { - let mut state = doc.app_state().lock().unwrap(); + let mut state = doc.app_state().lock_unpoisoned(); state.ensure_all_alive_containers(); state.store.encode(); // All the containers that are created after start_from need to be encoded @@ -144,7 +144,7 @@ pub(crate) fn export_state_only_snapshot( start_from: &Frontiers, w: &mut W, ) -> Result { - let oplog = doc.oplog().lock().unwrap(); + let oplog = doc.oplog().lock_unpoisoned(); let start_from = calc_shallow_doc_start(&oplog, start_from); let mut start_vv = oplog.dag().frontiers_to_vv(&start_from).unwrap(); for id in start_from.iter() { @@ -170,7 +170,7 @@ pub(crate) fn export_state_only_snapshot( drop(oplog); doc._checkout_without_emitting(&start_from, false, false) .unwrap(); - let mut state = doc.app_state().lock().unwrap(); + let mut state = doc.app_state().lock_unpoisoned(); let alive_containers = state.ensure_all_alive_containers(); let alive_c_bytes = cids_to_bytes(alive_containers); state.store.flush(); diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index bec367e76..2378de7df 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -1,5 +1,6 @@ use super::{state::DocState, txn::Transaction}; use crate::sync::Mutex; +use crate::sync::MutexExt as _; use crate::{ container::{ idx::ContainerIdx, @@ -80,7 +81,7 @@ pub trait HandlerTrait: Clone + Sized { method: "with_state", })?; let state = inner.doc.state.clone(); - let mut guard = state.lock().unwrap(); + let mut guard = state.lock_unpoisoned(); guard.with_state_mut(inner.container_idx, f) } } @@ -169,7 +170,7 @@ impl BasicHandler { #[inline] fn with_doc_state(&self, f: impl FnOnce(&mut DocState) -> R) -> R { let state = self.doc.state.clone(); - let mut guard = state.lock().unwrap(); + let mut guard = state.lock_unpoisoned(); f(&mut guard) } @@ -233,7 +234,7 @@ impl BasicHandler { } fn with_state(&self, f: impl FnOnce(&mut State) -> R) -> R { - let mut guard = self.doc.state.lock().unwrap(); + let mut guard = self.doc.state.lock_unpoisoned(); guard.with_state_mut(self.container_idx, f) } @@ -269,7 +270,7 @@ impl HandlerTrait for TextHandler { ) -> LoroResult { match &self.inner { MaybeDetached::Detached(t) => { - let mut t = t.lock().unwrap(); + let mut t = t.lock_unpoisoned(); let inner = create_handler(parent, self_id); let text = inner.into_text().unwrap(); let mut delta: Vec = Vec::new(); @@ -302,7 +303,7 @@ impl HandlerTrait for TextHandler { fn get_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); LoroValue::String((t.value.to_string()).into()) } MaybeDetached::Attached(a) => a.get_value(), @@ -323,7 +324,7 @@ impl HandlerTrait for TextHandler { fn get_attached(&self) -> Option { match &self.inner { - MaybeDetached::Detached(d) => d.lock().unwrap().attached.clone().map(|x| Self { + MaybeDetached::Detached(d) => d.lock_unpoisoned().attached.clone().map(|x| Self { inner: MaybeDetached::Attached(x), }), MaybeDetached::Attached(_a) => Some(self.clone()), @@ -476,7 +477,7 @@ impl HandlerTrait for MapHandler { fn get_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(m) => { - let m = m.lock().unwrap(); + let m = m.lock_unpoisoned(); let mut map = FxHashMap::default(); for (k, v) in m.value.iter() { map.insert(k.to_string(), v.to_value()); @@ -490,7 +491,7 @@ impl HandlerTrait for MapHandler { fn get_deep_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(m) => { - let m = m.lock().unwrap(); + let m = m.lock_unpoisoned(); let mut map = FxHashMap::default(); for (k, v) in m.value.iter() { map.insert(k.to_string(), v.to_deep_value()); @@ -517,7 +518,7 @@ impl HandlerTrait for MapHandler { ) -> LoroResult { match &self.inner { MaybeDetached::Detached(m) => { - let mut m = m.lock().unwrap(); + let mut m = m.lock_unpoisoned(); let inner = create_handler(parent, self_id); let map = inner.into_map().unwrap(); for (k, v) in m.value.iter() { @@ -552,7 +553,7 @@ impl HandlerTrait for MapHandler { fn get_attached(&self) -> Option { match &self.inner { - MaybeDetached::Detached(d) => d.lock().unwrap().attached.clone().map(|x| Self { + MaybeDetached::Detached(d) => d.lock_unpoisoned().attached.clone().map(|x| Self { inner: MaybeDetached::Attached(x), }), MaybeDetached::Attached(_a) => Some(self.clone()), @@ -608,7 +609,7 @@ impl HandlerTrait for MovableListHandler { fn get_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(a) => { - let a = a.lock().unwrap(); + let a = a.lock_unpoisoned(); LoroValue::List(a.value.iter().map(|v| v.to_value()).collect()) } MaybeDetached::Attached(a) => a.get_value(), @@ -618,7 +619,7 @@ impl HandlerTrait for MovableListHandler { fn get_deep_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(a) => { - let a = a.lock().unwrap(); + let a = a.lock_unpoisoned(); LoroValue::List(a.value.iter().map(|v| v.to_deep_value()).collect()) } MaybeDetached::Attached(a) => a.get_deep_value(), @@ -648,7 +649,7 @@ impl HandlerTrait for MovableListHandler { ) -> LoroResult { match &self.inner { MaybeDetached::Detached(l) => { - let mut l = l.lock().unwrap(); + let mut l = l.lock_unpoisoned(); let inner = create_handler(parent, self_id); let list = inner.into_movable_list().unwrap(); for (index, v) in l.value.iter().enumerate() { @@ -683,7 +684,7 @@ impl HandlerTrait for MovableListHandler { fn get_attached(&self) -> Option { match &self.inner { - MaybeDetached::Detached(d) => d.lock().unwrap().attached.clone().map(|x| Self { + MaybeDetached::Detached(d) => d.lock_unpoisoned().attached.clone().map(|x| Self { inner: MaybeDetached::Attached(x), }), MaybeDetached::Attached(_a) => Some(self.clone()), @@ -725,7 +726,7 @@ impl HandlerTrait for ListHandler { fn get_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(a) => { - let a = a.lock().unwrap(); + let a = a.lock_unpoisoned(); LoroValue::List(a.value.iter().map(|v| v.to_value()).collect()) } MaybeDetached::Attached(a) => a.get_value(), @@ -735,7 +736,7 @@ impl HandlerTrait for ListHandler { fn get_deep_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(a) => { - let a = a.lock().unwrap(); + let a = a.lock_unpoisoned(); LoroValue::List(a.value.iter().map(|v| v.to_deep_value()).collect()) } MaybeDetached::Attached(a) => a.get_deep_value(), @@ -758,7 +759,7 @@ impl HandlerTrait for ListHandler { ) -> LoroResult { match &self.inner { MaybeDetached::Detached(l) => { - let mut l = l.lock().unwrap(); + let mut l = l.lock_unpoisoned(); let inner = create_handler(parent, self_id); let list = inner.into_list().unwrap(); for (index, v) in l.value.iter().enumerate() { @@ -793,7 +794,7 @@ impl HandlerTrait for ListHandler { fn get_attached(&self) -> Option { match &self.inner { - MaybeDetached::Detached(d) => d.lock().unwrap().attached.clone().map(|x| Self { + MaybeDetached::Detached(d) => d.lock_unpoisoned().attached.clone().map(|x| Self { inner: MaybeDetached::Attached(x), }), MaybeDetached::Attached(_a) => Some(self.clone()), @@ -1226,9 +1227,7 @@ impl Handler { // Node exists but is deleted — resurrect it with the same // TreeID so that all references to the original node remain // valid (e.g. after undoing a tree.delete()). - x.create_at_with_target_for_apply_diff( - parent, position, target, - )?; + x.create_at_with_target_for_apply_diff(parent, position, target)?; } else { let new_target = x.__internal__next_tree_id(); if x.create_at_with_target_for_apply_diff( @@ -1266,9 +1265,7 @@ impl Handler { } } else if x.is_node_deleted(&target).unwrap() { // Node exists but is deleted — resurrect with same TreeID - x.create_at_with_target_for_apply_diff( - parent, position, target, - )?; + x.create_at_with_target_for_apply_diff(parent, position, target)?; } else { x.move_at_with_target_for_apply_diff(parent, position, target)?; } @@ -1371,7 +1368,7 @@ impl TextHandler { pub fn get_richtext_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.get_richtext_value() } MaybeDetached::Attached(a) => { @@ -1382,7 +1379,7 @@ impl TextHandler { pub fn is_empty(&self) -> bool { match &self.inner { - MaybeDetached::Detached(t) => t.lock().unwrap().value.is_empty(), + MaybeDetached::Detached(t) => t.lock_unpoisoned().value.is_empty(), MaybeDetached::Attached(a) => { a.with_state(|state| state.as_richtext_state_mut().unwrap().is_empty()) } @@ -1392,7 +1389,7 @@ impl TextHandler { pub fn len_utf8(&self) -> usize { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.len_utf8() } MaybeDetached::Attached(a) => { @@ -1404,7 +1401,7 @@ impl TextHandler { pub fn len_utf16(&self) -> usize { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.len_utf16() } MaybeDetached::Attached(a) => { @@ -1416,7 +1413,7 @@ impl TextHandler { pub fn len_unicode(&self) -> usize { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.len_unicode() } MaybeDetached::Attached(a) => { @@ -1437,7 +1434,7 @@ impl TextHandler { fn len(&self, pos_type: PosType) -> usize { match &self.inner { - MaybeDetached::Detached(t) => t.lock().unwrap().value.len(pos_type), + MaybeDetached::Detached(t) => t.lock_unpoisoned().value.len(pos_type), MaybeDetached::Attached(a) => { a.with_state(|state| state.as_richtext_state_mut().unwrap().len(pos_type)) } @@ -1447,7 +1444,7 @@ impl TextHandler { pub fn diagnose(&self) { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.diagnose(); } MaybeDetached::Attached(a) => { @@ -1459,7 +1456,7 @@ impl TextHandler { pub fn iter(&self, mut callback: impl FnMut(&str) -> bool) { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); for span in t.value.iter() { if !callback(span.text.as_str()) { return; @@ -1486,7 +1483,7 @@ impl TextHandler { } if let Ok(c) = match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); let event_pos = match pos_type { PosType::Event => pos, _ => t.value.index_to_event_index(pos, pos_type), @@ -1549,7 +1546,7 @@ impl TextHandler { let info = || format!("Position: {}:{}", file!(), line!()).into_boxed_str(); match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); let len = t.value.len(pos_type); if end_index > len { return Err(LoroError::OutOfBound { @@ -1597,7 +1594,7 @@ impl TextHandler { ) -> LoroResult> { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); let ans = t.value.slice_delta(start_index, end_index, pos_type)?; Ok(ans .into_iter() @@ -1656,7 +1653,7 @@ impl TextHandler { pub fn insert(&self, pos: usize, s: &str, pos_type: PosType) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => { - let mut t = t.lock().unwrap(); + let mut t = t.lock_unpoisoned(); let (index, _) = t .value .get_entity_index_for_text_insert(pos, pos_type) @@ -1713,7 +1710,7 @@ impl TextHandler { pub fn delete(&self, pos: usize, len: usize, pos_type: PosType) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => { - let mut t = t.lock().unwrap(); + let mut t = t.lock_unpoisoned(); let ranges = t.value.get_text_entity_ranges(pos, len, pos_type)?; for range in ranges.iter().rev() { t.value @@ -1981,7 +1978,7 @@ impl TextHandler { ) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => { - let mut g = t.lock().unwrap(); + let mut g = t.lock_unpoisoned(); self.mark_for_detached(&mut g.value, key, &value, start, end, pos_type) } MaybeDetached::Attached(a) => { @@ -2058,7 +2055,7 @@ impl TextHandler { ) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => self.mark_for_detached( - &mut t.lock().unwrap().value, + &mut t.lock_unpoisoned().value, key, &LoroValue::Null, start, @@ -2091,7 +2088,7 @@ impl TextHandler { let key: InternalString = key.into(); let is_delete = matches!(&value, &LoroValue::Null); - let mut doc_state = inner.doc.state.lock().unwrap(); + let mut doc_state = inner.doc.state.lock_unpoisoned(); let len = doc_state.with_state_mut(inner.container_idx, |state| { state.as_richtext_state_mut().unwrap().len(pos_type) }); @@ -2180,7 +2177,7 @@ impl TextHandler { pub fn check(&self) { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.check_consistency_between_content_and_style_ranges(); } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -2195,7 +2192,7 @@ impl TextHandler { pub fn apply_delta(&self, delta: &[TextDelta]) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => { - let _t = t.lock().unwrap(); + let _t = t.lock_unpoisoned(); // TODO: implement Err(LoroError::NotImplemented( "`apply_delta` on a detached text container", @@ -2336,7 +2333,7 @@ impl TextHandler { #[allow(clippy::inherent_to_string)] pub fn to_string(&self) -> String { match &self.inner { - MaybeDetached::Detached(t) => t.lock().unwrap().value.to_string(), + MaybeDetached::Detached(t) => t.lock_unpoisoned().value.to_string(), MaybeDetached::Attached(a) => a.get_value().into_string().unwrap().unwrap(), } } @@ -2427,7 +2424,7 @@ impl TextHandler { match &self.inner { MaybeDetached::Detached(s) => { let mut delta = Vec::new(); - for span in s.lock().unwrap().value.iter() { + for span in s.lock_unpoisoned().value.iter() { if span.text.as_str().is_empty() { continue; } @@ -2473,7 +2470,7 @@ impl TextHandler { pub fn clear(&self) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(mutex) => { - let mut t = mutex.lock().unwrap(); + let mut t = mutex.lock_unpoisoned(); let len = t.value.len_unicode(); let ranges = t.value.get_text_entity_ranges(0, len, PosType::Unicode)?; for range in ranges.iter().rev() { @@ -2505,7 +2502,7 @@ impl TextHandler { // Normalize to event + unicode indices for the given position. let (event_index, unicode_index) = match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); if index > t.value.len(from) { return None; } @@ -2555,7 +2552,7 @@ impl TextHandler { // Use the prefix text to compute target offset. let prefix = match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); if event_index > t.value.len_event() { return None; } @@ -2612,7 +2609,7 @@ impl ListHandler { pub fn insert(&self, pos: usize, v: impl Into) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(l) => { - let mut list = l.lock().unwrap(); + let mut list = l.lock_unpoisoned(); list.value.insert(pos, ValueOrHandler::Value(v.into())); Ok(()) } @@ -2659,7 +2656,7 @@ impl ListHandler { pub fn push(&self, v: impl Into) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(l) => { - let mut list = l.lock().unwrap(); + let mut list = l.lock_unpoisoned(); list.value.push(ValueOrHandler::Value(v.into())); Ok(()) } @@ -2675,7 +2672,7 @@ impl ListHandler { pub fn pop(&self) -> LoroResult> { match &self.inner { MaybeDetached::Detached(l) => { - let mut list = l.lock().unwrap(); + let mut list = l.lock_unpoisoned(); Ok(list.value.pop().map(|v| v.to_value())) } MaybeDetached::Attached(a) => a.with_txn(|txn| self.pop_with_txn(txn)), @@ -2696,7 +2693,7 @@ impl ListHandler { pub fn insert_container(&self, pos: usize, child: H) -> LoroResult { match &self.inner { MaybeDetached::Detached(l) => { - let mut list = l.lock().unwrap(); + let mut list = l.lock_unpoisoned(); list.value .insert(pos, ValueOrHandler::Handler(child.to_handler())); Ok(child) @@ -2745,7 +2742,7 @@ impl ListHandler { pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(l) => { - let mut list = l.lock().unwrap(); + let mut list = l.lock_unpoisoned(); list.value.drain(pos..pos + len); Ok(()) } @@ -2793,7 +2790,7 @@ impl ListHandler { pub fn get_child_handler(&self, index: usize) -> LoroResult { match &self.inner { MaybeDetached::Detached(l) => { - let list = l.lock().unwrap(); + let list = l.lock_unpoisoned(); let value = list.value.get(index).ok_or(LoroError::OutOfBound { pos: index, info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(), @@ -2836,7 +2833,7 @@ impl ListHandler { pub fn len(&self) -> usize { match &self.inner { - MaybeDetached::Detached(l) => l.lock().unwrap().value.len(), + MaybeDetached::Detached(l) => l.lock_unpoisoned().value.len(), MaybeDetached::Attached(a) => { a.with_state(|state| state.as_list_state().unwrap().len()) } @@ -2856,7 +2853,9 @@ impl ListHandler { pub fn get(&self, index: usize) -> Option { match &self.inner { - MaybeDetached::Detached(l) => l.lock().unwrap().value.get(index).map(|x| x.to_value()), + MaybeDetached::Detached(l) => { + l.lock_unpoisoned().value.get(index).map(|x| x.to_value()) + } MaybeDetached::Attached(a) => a.with_state(|state| { let a = state.as_list_state().unwrap(); a.get(index).cloned() @@ -2868,7 +2867,7 @@ impl ListHandler { pub fn get_(&self, index: usize) -> Option { match &self.inner { MaybeDetached::Detached(l) => { - let l = l.lock().unwrap(); + let l = l.lock_unpoisoned(); l.value.get(index).cloned() } MaybeDetached::Attached(inner) => { @@ -2891,7 +2890,7 @@ impl ListHandler { { match &self.inner { MaybeDetached::Detached(l) => { - let l = l.lock().unwrap(); + let l = l.lock_unpoisoned(); for v in l.value.iter() { f(v.clone()) } @@ -3030,7 +3029,7 @@ impl ListHandler { pub fn clear(&self) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(l) => { - let mut l = l.lock().unwrap(); + let mut l = l.lock_unpoisoned(); l.value.clear(); Ok(()) } @@ -3060,7 +3059,7 @@ impl MovableListHandler { pub fn insert(&self, pos: usize, v: impl Into) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); if pos > d.value.len() { return Err(LoroError::OutOfBound { pos, @@ -3123,7 +3122,7 @@ impl MovableListHandler { pub fn mov(&self, from: usize, to: usize) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); if from >= d.value.len() { return Err(LoroError::OutOfBound { pos: from, @@ -3204,7 +3203,7 @@ impl MovableListHandler { pub fn push(&self, v: LoroValue) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); d.value.push(v.into()); Ok(()) } @@ -3220,7 +3219,7 @@ impl MovableListHandler { pub fn pop_(&self) -> LoroResult> { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); Ok(d.value.pop()) } MaybeDetached::Attached(a) => { @@ -3235,7 +3234,7 @@ impl MovableListHandler { pub fn pop(&self) -> LoroResult> { match &self.inner { MaybeDetached::Detached(a) => { - let mut a = a.lock().unwrap(); + let mut a = a.lock_unpoisoned(); Ok(a.value.pop().map(|x| x.to_value())) } MaybeDetached::Attached(a) => a.with_txn(|txn| self.pop_with_txn(txn)), @@ -3256,7 +3255,7 @@ impl MovableListHandler { pub fn insert_container(&self, pos: usize, child: H) -> LoroResult { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); if pos > d.value.len() { return Err(LoroError::OutOfBound { pos, @@ -3318,7 +3317,7 @@ impl MovableListHandler { pub fn set(&self, index: usize, value: impl Into) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); if index >= d.value.len() { return Err(LoroError::OutOfBound { pos: index, @@ -3370,7 +3369,7 @@ impl MovableListHandler { pub fn set_container(&self, pos: usize, child: H) -> LoroResult { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); d.value[pos] = ValueOrHandler::Handler(child.to_handler()); Ok(child) } @@ -3425,7 +3424,7 @@ impl MovableListHandler { pub fn delete(&self, pos: usize, len: usize) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); d.value.drain(pos..pos + len); Ok(()) } @@ -3487,7 +3486,7 @@ impl MovableListHandler { pub fn get_child_handler(&self, index: usize) -> LoroResult { match &self.inner { MaybeDetached::Detached(l) => { - let list = l.lock().unwrap(); + let list = l.lock_unpoisoned(); let value = list.value.get(index).ok_or(LoroError::OutOfBound { pos: index, info: format!("Position: {}:{}", file!(), line!()).into_boxed_str(), @@ -3536,7 +3535,7 @@ impl MovableListHandler { pub fn len(&self) -> usize { match &self.inner { MaybeDetached::Detached(d) => { - let d = d.lock().unwrap(); + let d = d.lock_unpoisoned(); d.value.len() } MaybeDetached::Attached(a) => { @@ -3562,7 +3561,7 @@ impl MovableListHandler { pub fn get(&self, index: usize) -> Option { match &self.inner { MaybeDetached::Detached(d) => { - let d = d.lock().unwrap(); + let d = d.lock_unpoisoned(); d.value.get(index).map(|v| v.to_value()) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -3576,7 +3575,7 @@ impl MovableListHandler { pub fn get_(&self, index: usize) -> Option { match &self.inner { MaybeDetached::Detached(d) => { - let d = d.lock().unwrap(); + let d = d.lock_unpoisoned(); d.value.get(index).cloned() } MaybeDetached::Attached(m) => m.with_state(|state| { @@ -3601,7 +3600,7 @@ impl MovableListHandler { { match &self.inner { MaybeDetached::Detached(d) => { - let d = d.lock().unwrap(); + let d = d.lock_unpoisoned(); for v in d.value.iter() { f(v.clone()); } @@ -3632,7 +3631,7 @@ impl MovableListHandler { pub fn log_internal_state(&self) -> String { match &self.inner { MaybeDetached::Detached(d) => { - let d = d.lock().unwrap(); + let d = d.lock_unpoisoned(); format!("{:#?}", &d.value) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -3716,7 +3715,7 @@ impl MovableListHandler { pub fn clear(&self) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(d) => { - let mut d = d.lock().unwrap(); + let mut d = d.lock_unpoisoned(); d.value.clear(); Ok(()) } @@ -3775,7 +3774,7 @@ impl MapHandler { pub fn insert(&self, key: &str, value: impl Into) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(m) => { - let mut m = m.lock().unwrap(); + let mut m = m.lock_unpoisoned(); m.value .insert(key.into(), ValueOrHandler::Value(value.into())); Ok(()) @@ -3790,7 +3789,7 @@ impl MapHandler { fn insert_without_skipping(&self, key: &str, value: impl Into) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(m) => { - let mut m = m.lock().unwrap(); + let mut m = m.lock_unpoisoned(); m.value .insert(key.into(), ValueOrHandler::Value(value.into())); Ok(()) @@ -3860,7 +3859,7 @@ impl MapHandler { pub fn insert_container(&self, key: &str, handler: T) -> LoroResult { match &self.inner { MaybeDetached::Detached(m) => { - let mut m = m.lock().unwrap(); + let mut m = m.lock_unpoisoned(); let to_insert = handler.to_handler(); m.value .insert(key.into(), ValueOrHandler::Handler(to_insert.clone())); @@ -3900,7 +3899,7 @@ impl MapHandler { pub fn delete(&self, key: &str) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(m) => { - let mut m = m.lock().unwrap(); + let mut m = m.lock_unpoisoned(); m.value.remove(key); Ok(()) } @@ -3930,7 +3929,7 @@ impl MapHandler { { match &self.inner { MaybeDetached::Detached(m) => { - let m = m.lock().unwrap(); + let m = m.lock_unpoisoned(); for (k, v) in m.value.iter() { f(k, v.clone()); } @@ -3966,7 +3965,7 @@ impl MapHandler { pub fn get_child_handler(&self, key: &str) -> LoroResult { match &self.inner { MaybeDetached::Detached(m) => { - let m = m.lock().unwrap(); + let m = m.lock_unpoisoned(); let value = m.value.get(key).unwrap(); match value { ValueOrHandler::Value(v) => Err(LoroError::ArgErr( @@ -4006,7 +4005,7 @@ impl MapHandler { pub fn get(&self, key: &str) -> Option { match &self.inner { MaybeDetached::Detached(m) => { - let m = m.lock().unwrap(); + let m = m.lock_unpoisoned(); m.value.get(key).map(|v| v.to_value()) } MaybeDetached::Attached(inner) => { @@ -4019,7 +4018,7 @@ impl MapHandler { pub fn get_(&self, key: &str) -> Option { match &self.inner { MaybeDetached::Detached(m) => { - let m = m.lock().unwrap(); + let m = m.lock_unpoisoned(); m.value.get(key).cloned() } MaybeDetached::Attached(inner) => { @@ -4065,7 +4064,7 @@ impl MapHandler { pub fn len(&self) -> usize { match &self.inner { - MaybeDetached::Detached(m) => m.lock().unwrap().value.len(), + MaybeDetached::Detached(m) => m.lock_unpoisoned().value.len(), MaybeDetached::Attached(a) => a.with_state(|state| state.as_map_state().unwrap().len()), } } @@ -4084,7 +4083,7 @@ impl MapHandler { pub fn clear(&self) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(m) => { - let mut m = m.lock().unwrap(); + let mut m = m.lock_unpoisoned(); m.value.clear(); Ok(()) } @@ -4113,7 +4112,7 @@ impl MapHandler { let mut keys: Vec = Vec::with_capacity(self.len()); match &self.inner { MaybeDetached::Detached(m) => { - let m = m.lock().unwrap(); + let m = m.lock_unpoisoned(); keys = m.value.keys().map(|x| x.as_str().into()).collect(); } MaybeDetached::Attached(a) => { @@ -4134,7 +4133,7 @@ impl MapHandler { let mut values: Vec = Vec::with_capacity(self.len()); match &self.inner { MaybeDetached::Detached(m) => { - let m = m.lock().unwrap(); + let m = m.lock_unpoisoned(); values = m.value.values().cloned().collect(); } MaybeDetached::Attached(a) => { @@ -4169,7 +4168,7 @@ impl MapHandler { fn with_txn(doc: &LoroDoc, f: impl FnOnce(&mut Transaction) -> LoroResult) -> LoroResult { let txn = &doc.txn; - let mut txn = txn.lock().unwrap(); + let mut txn = txn.lock_unpoisoned(); loop { if let Some(txn) = &mut *txn { return f(txn); @@ -4180,7 +4179,7 @@ fn with_txn(doc: &LoroDoc, f: impl FnOnce(&mut Transaction) -> LoroResult) #[cfg(loom)] loom::thread::yield_now(); doc.start_auto_commit(); - txn = doc.txn.lock().unwrap(); + txn = doc.txn.lock_unpoisoned(); } } } @@ -4212,7 +4211,7 @@ pub mod counter { pub fn increment(&self, n: f64) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(d) => { - let d = &mut d.lock().unwrap().value; + let d = &mut d.lock_unpoisoned().value; *d += n; Ok(()) } @@ -4223,7 +4222,7 @@ pub mod counter { pub fn decrement(&self, n: f64) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(d) => { - let d = &mut d.lock().unwrap().value; + let d = &mut d.lock_unpoisoned().value; *d -= n; Ok(()) } @@ -4274,7 +4273,7 @@ pub mod counter { fn get_value(&self) -> loro_common::LoroValue { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.into() } MaybeDetached::Attached(a) => a.get_value(), @@ -4308,7 +4307,7 @@ pub mod counter { ) -> loro_common::LoroResult { match &self.inner { MaybeDetached::Detached(v) => { - let mut v = v.lock().unwrap(); + let mut v = v.lock_unpoisoned(); let inner = create_handler(parent, self_id); let c = inner.into_counter().unwrap(); @@ -4465,7 +4464,8 @@ mod test { let doc_b = LoroDoc::new(); doc_b.set_peer_id(2).unwrap(); - doc_b.import(&doc_a.export(ExportMode::all_updates()).unwrap()) + doc_b + .import(&doc_a.export(ExportMode::all_updates()).unwrap()) .unwrap(); let list_b = doc_b.get_list("list"); diff --git a/crates/loro-internal/src/handler/tree.rs b/crates/loro-internal/src/handler/tree.rs index 36e5829e2..0131944ba 100644 --- a/crates/loro-internal/src/handler/tree.rs +++ b/crates/loro-internal/src/handler/tree.rs @@ -1,5 +1,6 @@ use std::{collections::VecDeque, sync::Arc}; +use crate::sync::MutexExt as _; use fractional_index::FractionalIndex; use loro_common::{ ContainerID, ContainerType, Counter, IdLp, LoroError, LoroResult, LoroTreeError, LoroValue, @@ -162,7 +163,7 @@ impl HandlerTrait for TreeHandler { ) -> LoroResult { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); let inner = create_handler(parent, self_id); let tree = inner.into_tree().unwrap(); @@ -223,7 +224,7 @@ impl HandlerTrait for TreeHandler { fn get_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.get_value(false) } MaybeDetached::Attached(a) => a.get_value(), @@ -233,7 +234,7 @@ impl HandlerTrait for TreeHandler { fn get_deep_value(&self) -> LoroValue { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.get_value(true) } MaybeDetached::Attached(a) => a.get_deep_value(), @@ -246,7 +247,7 @@ impl HandlerTrait for TreeHandler { fn get_attached(&self) -> Option { match &self.inner { - MaybeDetached::Detached(d) => d.lock().unwrap().attached.clone().map(|x| Self { + MaybeDetached::Detached(d) => d.lock_unpoisoned().attached.clone().map(|x| Self { inner: MaybeDetached::Attached(x), }), MaybeDetached::Attached(_a) => Some(self.clone()), @@ -292,7 +293,7 @@ impl TreeHandler { pub fn delete(&self, target: TreeID) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => { - let mut t = t.lock().unwrap(); + let mut t = t.lock_unpoisoned(); t.value.delete(target)?; Ok(()) } @@ -332,7 +333,7 @@ impl TreeHandler { let index: usize = self.children_num(&parent).unwrap_or(0); match &self.inner { MaybeDetached::Detached(t) => { - let t = &mut t.lock().unwrap().value; + let t = &mut t.lock_unpoisoned().value; Ok(t.create(parent.tree_id(), index)) } MaybeDetached::Attached(a) => { @@ -344,7 +345,7 @@ impl TreeHandler { pub fn create_at(&self, parent: TreeParentId, index: usize) -> LoroResult { match &self.inner { MaybeDetached::Detached(t) => { - let t = &mut t.lock().unwrap().value; + let t = &mut t.lock_unpoisoned().value; Ok(t.create(parent.tree_id(), index)) } MaybeDetached::Attached(a) => { @@ -594,7 +595,7 @@ impl TreeHandler { pub fn move_to(&self, target: TreeID, parent: TreeParentId, index: usize) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => { - let mut t = t.lock().unwrap(); + let mut t = t.lock_unpoisoned(); t.value.mov(target, parent.tree_id(), index) } MaybeDetached::Attached(a) => a.with_txn(|txn| { @@ -725,7 +726,7 @@ impl TreeHandler { pub fn get_meta(&self, target: TreeID) -> LoroResult { match &self.inner { MaybeDetached::Detached(d) => { - let d = d.lock().unwrap(); + let d = d.lock_unpoisoned(); d.value .map .get(&target) @@ -746,7 +747,7 @@ impl TreeHandler { pub fn is_node_unexist(&self, target: &TreeID) -> bool { match &self.inner { MaybeDetached::Detached(d) => { - let d = d.lock().unwrap(); + let d = d.lock_unpoisoned(); !d.value.map.contains_key(target) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -759,7 +760,7 @@ impl TreeHandler { pub fn is_node_deleted(&self, target: &TreeID) -> LoroResult { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value .map .get(target) @@ -778,7 +779,7 @@ impl TreeHandler { pub fn get_node_parent(&self, target: &TreeID) -> Option { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.get_parent(target).map(TreeParentId::from) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -792,7 +793,7 @@ impl TreeHandler { pub fn children(&self, parent: &TreeParentId) -> Option> { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.get_children(parent.tree_id()) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -805,7 +806,7 @@ impl TreeHandler { pub fn children_num(&self, parent: &TreeParentId) -> Option { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.children_num(parent.tree_id()) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -819,7 +820,7 @@ impl TreeHandler { pub fn contains(&self, target: TreeID) -> bool { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.map.contains_key(&target) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -832,7 +833,7 @@ impl TreeHandler { pub fn get_child_at(&self, parent: &TreeParentId, index: usize) -> Option { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.get_id_by_index(&parent.tree_id(), index) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -845,7 +846,7 @@ impl TreeHandler { pub fn is_parent(&self, target: &TreeID, parent: &TreeParentId) -> bool { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.is_parent(target, &parent.tree_id()) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -859,7 +860,7 @@ impl TreeHandler { pub fn nodes(&self) -> Vec { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.map.keys().cloned().collect() } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -901,7 +902,7 @@ impl TreeHandler { pub fn __internal__next_tree_id(&self) -> TreeID { match &self.inner { MaybeDetached::Detached(d) => { - let d = d.lock().unwrap(); + let d = d.lock_unpoisoned(); TreeID::new(PeerID::MAX, d.value.next_counter) } MaybeDetached::Attached(a) => a @@ -932,7 +933,7 @@ impl TreeHandler { pub fn get_index_by_tree_id(&self, target: &TreeID) -> Option { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.get_index_by_tree_id(target) } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -1045,7 +1046,7 @@ impl TreeHandler { pub fn is_empty(&self) -> bool { match &self.inner { MaybeDetached::Detached(t) => { - let t = t.lock().unwrap(); + let t = t.lock_unpoisoned(); t.value.map.is_empty() } MaybeDetached::Attached(a) => a.with_state(|state| { @@ -1068,7 +1069,7 @@ impl TreeHandler { pub fn clear(&self) -> LoroResult<()> { match &self.inner { MaybeDetached::Detached(t) => { - let mut t = t.lock().unwrap(); + let mut t = t.lock_unpoisoned(); t.value.map.clear(); t.value.children_links.clear(); t.value.parent_links.clear(); diff --git a/crates/loro-internal/src/history_cache.rs b/crates/loro-internal/src/history_cache.rs index 490f8a9d8..5ac8fb33c 100644 --- a/crates/loro-internal/src/history_cache.rs +++ b/crates/loro-internal/src/history_cache.rs @@ -6,6 +6,7 @@ use std::{ }; use crate::sync::Mutex; +use crate::sync::MutexExt as _; use either::Either; use enum_as_inner::EnumAsInner; use enum_dispatch::enum_dispatch; @@ -201,7 +202,7 @@ impl ContainerHistoryCache { ensure_cov::notify_cov( "loro_internal::history_cache::init_cache_by_visit_all_change_slow::visit_gc", ); - let mut store = state.store.lock().unwrap(); + let mut store = state.store.lock_unpoisoned(); for (idx, c) in store.iter_all_containers_mut() { match idx.get_type() { ContainerType::Text | ContainerType::List | ContainerType::Unknown(_) => { @@ -311,7 +312,7 @@ impl ContainerHistoryCache { return Vec::new(); }; - let mut binding = state.store.lock().unwrap(); + let mut binding = state.store.lock_unpoisoned(); let Some(text) = binding.get_mut(idx) else { return Vec::new(); }; @@ -351,7 +352,7 @@ impl ContainerHistoryCache { return Vec::new(); }; - let mut binding = state.store.lock().unwrap(); + let mut binding = state.store.lock_unpoisoned(); let Some(list) = binding.get_mut(idx) else { return Vec::new(); }; @@ -633,7 +634,7 @@ impl TreeOpGroup { } pub(crate) fn record_shallow_root_state(&mut self, nodes: Vec) { - let mut tree = self.tree_for_diff.lock().unwrap(); + let mut tree = self.tree_for_diff.lock_unpoisoned(); for node in nodes.iter() { self.ops.insert( node.id.idlp(), diff --git a/crates/loro-internal/src/jsonpath/jsonpath_impl.rs b/crates/loro-internal/src/jsonpath/jsonpath_impl.rs index 4c2c30082..919343979 100644 --- a/crates/loro-internal/src/jsonpath/jsonpath_impl.rs +++ b/crates/loro-internal/src/jsonpath/jsonpath_impl.rs @@ -5,6 +5,7 @@ use crate::jsonpath::ast::{ ComparisonOperator, FilterExpression, LogicalOperator, Segment, Selector, }; use crate::jsonpath::JSONPathParser; +use crate::sync::MutexExt as _; use crate::{HandlerTrait, LoroDoc}; use loro_common::{ContainerID, LoroValue}; use std::ops::ControlFlow; @@ -526,7 +527,7 @@ impl PathValue for LoroDoc { } fn for_each_for_path(&self, f: &mut dyn FnMut(ValueOrHandler) -> ControlFlow<()>) { - let x = self.state.lock().unwrap().store.load_all(); + let x = self.state.lock_unpoisoned().store.load_all(); let arena = self.arena(); for c in arena.root_containers(x) { let cid = arena.idx_to_id(c).unwrap(); @@ -538,8 +539,8 @@ impl PathValue for LoroDoc { } fn length_for_path(&self) -> usize { - let x = self.state.lock().unwrap().store.load_all(); - let state = self.app_state().lock().unwrap(); + let x = self.state.lock_unpoisoned().store.load_all(); + let state = self.app_state().lock_unpoisoned(); state.arena.root_containers(x).len() } diff --git a/crates/loro-internal/src/lock.rs b/crates/loro-internal/src/lock.rs index cf5c5f2a2..346674a26 100644 --- a/crates/loro-internal/src/lock.rs +++ b/crates/loro-internal/src/lock.rs @@ -159,6 +159,13 @@ impl LoroMutex { Ok(ans) } + pub fn lock_unpoisoned(&self) -> LoroMutexGuard<'_, T> { + match self.lock() { + Ok(guard) => guard, + Err(_) => unreachable!("LoroMutex::lock recovers poisoned state before returning"), + } + } + /// Returns whether the mutex appears locked at this instant. /// /// This is implemented via `try_lock().is_err()` and is intended only for @@ -254,8 +261,8 @@ mod tests { let mutex1 = group.new_lock(1, LockKind::DocState); let mutex2 = group.new_lock(2, LockKind::Txn); - let _guard1 = mutex1.lock().unwrap(); // Lock higher priority first - let _guard2 = mutex2.lock().unwrap(); // This should panic with caller info + let _guard1 = mutex1.lock_unpoisoned(); // Lock higher priority first + let _guard2 = mutex2.lock_unpoisoned(); // This should panic with caller info } #[test] @@ -264,11 +271,11 @@ mod tests { let mutex1 = group.new_lock(1, LockKind::Txn); let mutex2 = group.new_lock(2, LockKind::OpLog); let mutex3 = group.new_lock(3, LockKind::DocState); - let _guard1 = mutex1.lock().unwrap(); + let _guard1 = mutex1.lock_unpoisoned(); drop(_guard1); - let _guard2 = mutex2.lock().unwrap(); + let _guard2 = mutex2.lock_unpoisoned(); drop(_guard2); - let _guard3 = mutex3.lock().unwrap(); + let _guard3 = mutex3.lock_unpoisoned(); } #[test] @@ -277,8 +284,8 @@ mod tests { let group = LoroLockGroup::new(); let mutex1 = group.new_lock(1, LockKind::Txn); let mutex2 = group.new_lock(2, LockKind::OpLog); - let _guard1 = mutex1.lock().unwrap(); - let _guard2 = mutex2.lock().unwrap(); + let _guard1 = mutex1.lock_unpoisoned(); + let _guard2 = mutex2.lock_unpoisoned(); drop(_guard1); drop(_guard2); } @@ -289,10 +296,10 @@ mod tests { let mutex1 = group.new_lock(1, LockKind::Txn); let mutex2 = group.new_lock(2, LockKind::OpLog); let mutex3 = group.new_lock(3, LockKind::DocState); - let _guard1 = mutex1.lock().unwrap(); - let _guard3 = mutex3.lock().unwrap(); + let _guard1 = mutex1.lock_unpoisoned(); + let _guard3 = mutex3.lock_unpoisoned(); drop(_guard3); - let _guard2 = mutex2.lock().unwrap(); + let _guard2 = mutex2.lock_unpoisoned(); drop(_guard2); } @@ -303,10 +310,10 @@ mod tests { let mutex1 = group.new_lock(1, LockKind::Txn); let mutex2 = group.new_lock(2, LockKind::OpLog); let mutex3 = group.new_lock(3, LockKind::DocState); - let _guard2 = mutex2.lock().unwrap(); - let _guard3 = mutex3.lock().unwrap(); + let _guard2 = mutex2.lock_unpoisoned(); + let _guard3 = mutex3.lock_unpoisoned(); drop(_guard3); - let _guard1 = mutex1.lock().unwrap(); + let _guard1 = mutex1.lock_unpoisoned(); } #[test] @@ -315,12 +322,12 @@ mod tests { let mutex1 = group.new_lock(1, LockKind::Txn); let mutex2 = group.new_lock(2, LockKind::Txn); - let guard1 = mutex1.lock().unwrap(); + let guard1 = mutex1.lock_unpoisoned(); // Locking same kind should work (cur >= self.kind, so this would fail) // Actually, let's test this properly - same kind should fail drop(guard1); - let _guard2 = mutex2.lock().unwrap(); // This should work when guard1 is dropped + let _guard2 = mutex2.lock_unpoisoned(); // This should work when guard1 is dropped } #[test] @@ -339,7 +346,7 @@ mod tests { assert!(!mutex.is_locked()); - let _guard = mutex.lock().unwrap(); + let _guard = mutex.lock_unpoisoned(); assert!(mutex.is_locked()); } @@ -351,10 +358,10 @@ mod tests { let mutex1 = group.new_lock(1, LockKind::DocState); let mutex2 = group.new_lock(2, LockKind::Txn); - let _guard1 = mutex1.lock().unwrap(); + let _guard1 = mutex1.lock_unpoisoned(); // This line should be reported in the panic message - let _guard2 = mutex2.lock().unwrap(); + let _guard2 = mutex2.lock_unpoisoned(); } #[test] @@ -363,11 +370,11 @@ mod tests { let mutex = group.new_lock(42, LockKind::Txn); let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { - let _guard = mutex.lock().unwrap(); + let _guard = mutex.lock_unpoisoned(); panic!("poison the lock"); })); - let guard = mutex.lock().unwrap(); + let guard = mutex.lock_unpoisoned(); assert_eq!(*guard, 42); } } diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index 1f938cda2..f2dde8c79 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -182,9 +182,9 @@ impl LoroDoc { if peer == PeerID::MAX { return Err(LoroError::InvalidPeerID); } - let next_id = self.oplog.lock().unwrap().next_id(peer); + let next_id = self.oplog.lock_unpoisoned().next_id(peer); if self.auto_commit.load(Acquire) { - let doc_state = self.state.lock().unwrap(); + let doc_state = self.state.lock_unpoisoned(); doc_state .peer .store(peer, std::sync::atomic::Ordering::Relaxed); @@ -198,7 +198,7 @@ impl LoroDoc { return Ok(()); } - let doc_state = self.state.lock().unwrap(); + let doc_state = self.state.lock_unpoisoned(); if doc_state.is_in_txn() { return Err(LoroError::TransactionError( "Cannot change peer id during transaction" @@ -261,7 +261,7 @@ impl LoroDoc { /// /// It return Some(txn) if the txn is None fn before_commit(&self) -> Option>> { - let mut txn_guard = self.txn.lock().unwrap(); + let mut txn_guard = self.txn.lock_unpoisoned(); let Some(txn) = txn_guard.as_mut() else { return Some(txn_guard); }; @@ -298,7 +298,7 @@ impl LoroDoc { Option>>, ) { if !self.auto_commit.load(Acquire) { - let txn_guard = self.txn.lock().unwrap(); + let txn_guard = self.txn.lock_unpoisoned(); // if not auto_commit, nothing should happen // because the global txn is not used return (None, Some(txn_guard)); @@ -309,7 +309,7 @@ impl LoroDoc { return (None, Some(txn_guard)); } - let mut txn_guard = self.txn.lock().unwrap(); + let mut txn_guard = self.txn.lock_unpoisoned(); let txn = txn_guard.take(); let Some(mut txn) = txn else { return (None, Some(txn_guard)); @@ -352,7 +352,7 @@ impl LoroDoc { if let Some(on_commit) = on_commit { drop(txn_guard); on_commit(&self.state, &self.oplog, id_span); - txn_guard = self.txn.lock().unwrap(); + txn_guard = self.txn.lock_unpoisoned(); if !config.immediate_renew && txn_guard.is_some() { // make sure that txn_guard is None when config.immediate_renew is false continue; @@ -387,7 +387,7 @@ impl LoroDoc { /// Set the commit message of the next commit pub fn set_next_commit_message(&self, message: &str) { - let mut binding = self.txn.lock().unwrap(); + let mut binding = self.txn.lock_unpoisoned(); let Some(txn) = binding.as_mut() else { return; }; @@ -401,7 +401,7 @@ impl LoroDoc { /// Set the origin of the next commit pub fn set_next_commit_origin(&self, origin: &str) { - let mut txn = self.txn.lock().unwrap(); + let mut txn = self.txn.lock_unpoisoned(); if let Some(txn) = txn.as_mut() { txn.set_origin(origin.into()); } @@ -409,7 +409,7 @@ impl LoroDoc { /// Set the timestamp of the next commit pub fn set_next_commit_timestamp(&self, timestamp: Timestamp) { - let mut txn = self.txn.lock().unwrap(); + let mut txn = self.txn.lock_unpoisoned(); if let Some(txn) = txn.as_mut() { txn.set_timestamp(timestamp); } @@ -417,7 +417,7 @@ impl LoroDoc { /// Set the options of the next commit pub fn set_next_commit_options(&self, options: CommitOptions) { - let mut txn = self.txn.lock().unwrap(); + let mut txn = self.txn.lock_unpoisoned(); if let Some(txn) = txn.as_mut() { txn.set_options(options); } @@ -425,7 +425,7 @@ impl LoroDoc { /// Clear the options of the next commit pub fn clear_next_commit_options(&self) { - let mut txn = self.txn.lock().unwrap(); + let mut txn = self.txn.lock_unpoisoned(); if let Some(txn) = txn.as_mut() { txn.set_options(CommitOptions::new()); } @@ -495,7 +495,7 @@ impl LoroDoc { /// Is the document empty? (no ops) #[inline(always)] pub fn can_reset_with_snapshot(&self) -> bool { - let oplog = self.oplog.lock().unwrap(); + let oplog = self.oplog.lock_unpoisoned(); if oplog.batch_importing { return false; } @@ -504,7 +504,7 @@ impl LoroDoc { return false; } - oplog.is_empty() && self.state.lock().unwrap().can_import_snapshot() + oplog.is_empty() && self.state.lock_unpoisoned().can_import_snapshot() } /// Whether [OpLog] and [DocState] are detached. @@ -544,8 +544,8 @@ impl LoroDoc { /// It's the last edit time of the [DocState]. pub fn state_timestamp(&self) -> Timestamp { // Acquire locks in correct order: read frontiers first, then query OpLog. - let f = { self.state.lock().unwrap().frontiers.clone() }; - self.oplog.lock().unwrap().get_timestamp_of_version(&f) + let f = { self.state.lock_unpoisoned().frontiers.clone() }; + self.oplog.lock_unpoisoned().get_timestamp_of_version(&f) } #[inline(always)] @@ -555,7 +555,7 @@ impl LoroDoc { #[inline] pub fn get_state_deep_value(&self) -> LoroValue { - self.state.lock().unwrap().get_deep_value() + self.state.lock_unpoisoned().get_deep_value() } #[inline(always)] @@ -590,7 +590,7 @@ impl LoroDoc { loro_common::info!("Importing with mode={:?}", &parsed.mode); let result = match parsed.mode { EncodeMode::OutdatedRle => { - if self.state.lock().unwrap().is_in_txn() { + if self.state.lock_unpoisoned().is_in_txn() { return Err(LoroError::ImportWhenInTxn); } @@ -653,7 +653,7 @@ impl LoroDoc { f: impl FnOnce(&mut OpLog) -> Result, origin: InternalString, ) -> Result { - let mut oplog = self.oplog.lock().unwrap(); + let mut oplog = self.oplog.lock_unpoisoned(); if !self.is_detached() { let old_vv = oplog.vv().clone(); let old_frontiers = oplog.frontiers().clone(); @@ -668,7 +668,7 @@ impl LoroDoc { oplog.dag.get_frontiers(), None, ); - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); state.apply_diff( InternalDocDiff { origin, @@ -688,7 +688,7 @@ impl LoroDoc { fn emit_events(&self) { // we should not hold the lock when emitting events let events = { - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); state.take_events() }; for event in events { @@ -697,7 +697,7 @@ impl LoroDoc { } pub(crate) fn drop_pending_events(&self) -> Vec { - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); state.take_events() } @@ -724,7 +724,7 @@ impl LoroDoc { with_peer_compression: bool, ) -> JsonSchema { self.with_barrier(|| { - let oplog = self.oplog.lock().unwrap(); + let oplog = self.oplog.lock_unpoisoned(); let mut start_vv = start_vv; let _temp: Option; if !oplog.dag.shallow_since_vv().is_empty() { @@ -756,7 +756,7 @@ impl LoroDoc { } pub fn export_json_in_id_span(&self, id_span: IdSpan) -> Vec { - let oplog = self.oplog.lock().unwrap(); + let oplog = self.oplog.lock_unpoisoned(); let mut changes = export_json_in_id_span(&oplog, id_span); if let Some(uncommit) = oplog.get_uncommitted_change_in_span(id_span) { let change_json = encode_change(ChangeRef::from_change(&uncommit), &self.arena, None); @@ -768,19 +768,19 @@ impl LoroDoc { /// Get the version vector of the current OpLog #[inline] pub fn oplog_vv(&self) -> VersionVector { - self.oplog.lock().unwrap().vv().clone() + self.oplog.lock_unpoisoned().vv().clone() } /// Get the version vector of the current [DocState] #[inline] pub fn state_vv(&self) -> VersionVector { - let oplog = self.oplog.lock().unwrap(); - let f = &self.state.lock().unwrap().frontiers; + let oplog = self.oplog.lock_unpoisoned(); + let f = &self.state.lock_unpoisoned().frontiers; oplog.dag.frontiers_to_vv(f).unwrap() } pub fn get_by_path(&self, path: &[Index]) -> Option { - let value: LoroValue = self.state.lock().unwrap().get_value_by_path(path)?; + let value: LoroValue = self.state.lock_unpoisoned().get_value_by_path(path)?; if let LoroValue::Container(c) = value { Some(ValueOrHandler::Handler(Handler::new_attached( c.clone(), @@ -799,7 +799,7 @@ impl LoroDoc { pub fn get_uncommitted_ops_as_json(&self) -> Option { let arena = &self.arena; - let txn = self.txn.lock().unwrap(); + let txn = self.txn.lock_unpoisoned(); let txn = txn.as_ref()?; let ops_ = txn.local_ops(); let new_id = ID { @@ -813,7 +813,7 @@ impl LoroDoc { .timestamp() .as_ref() .copied() - .unwrap_or_else(|| self.oplog.lock().unwrap().get_timestamp_for_next_txn()), + .unwrap_or_else(|| self.oplog.lock_unpoisoned().get_timestamp_for_next_txn()), commit_msg: txn.msg(), ops: ops_, lamport: txn.lamport(), @@ -896,7 +896,7 @@ impl LoroDoc { return true; } - let exist = self.state.lock().unwrap().does_container_exist(id); + let exist = self.state.lock_unpoisoned().does_container_exist(id); exist } @@ -938,13 +938,16 @@ impl LoroDoc { } let (was_recording, latest_frontiers) = { - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); let was_recording = state.is_recording(); state.stop_and_clear_recording(); (was_recording, state.frontiers.clone()) }; - let spans = self.oplog.lock().unwrap().split_span_based_on_deps(id_span); + let spans = self + .oplog + .lock_unpoisoned() + .split_span_based_on_deps(id_span); let diff = crate::undo::undo( spans, match post_transform_base { @@ -953,9 +956,9 @@ impl LoroDoc { }, |from, to| { self._checkout_without_emitting(from, false, false).unwrap(); - self.state.lock().unwrap().start_recording(); + self.state.lock_unpoisoned().start_recording(); self._checkout_without_emitting(to, false, false).unwrap(); - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); let e = state.take_events(); state.stop_and_clear_recording(); DiffBatch::new(e) @@ -969,7 +972,7 @@ impl LoroDoc { self._checkout_without_emitting(&latest_frontiers, false, false)?; self.set_detached(false); if was_recording { - self.state.lock().unwrap().start_recording(); + self.state.lock_unpoisoned().start_recording(); } drop(txn); self.start_auto_commit(); @@ -1010,7 +1013,7 @@ impl LoroDoc { { // Check whether a and b are valid before checkout so this returns a normal error // instead of panicking on shallow docs. - let oplog = self.oplog.lock().unwrap(); + let oplog = self.oplog.lock_unpoisoned(); let validate_frontiers = |frontiers: &Frontiers| -> LoroResult<()> { for id in frontiers.iter() { if !oplog.dag.contains(id) { @@ -1033,16 +1036,16 @@ impl LoroDoc { let was_detached = self.is_detached(); let old_frontiers = self.state_frontiers(); let was_recording = { - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); let is_recording = state.is_recording(); state.stop_and_clear_recording(); is_recording }; self._checkout_without_emitting(a, true, false).unwrap(); - self.state.lock().unwrap().start_recording(); + self.state.lock_unpoisoned().start_recording(); self._checkout_without_emitting(b, true, false).unwrap(); let e = { - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); let e = state.take_events(); state.stop_and_clear_recording(); e @@ -1055,7 +1058,7 @@ impl LoroDoc { self.renew_txn_if_auto_commit(options); } if was_recording { - self.state.lock().unwrap().start_recording(); + self.state.lock_unpoisoned().start_recording(); } Ok(DiffBatch::new(e)) } @@ -1098,16 +1101,16 @@ impl LoroDoc { if matches!(&id, ContainerID::Normal { .. }) && self.arena.id_to_idx(&id).is_none() { // Not in arena does not imply non-existent; consult state/kv and register lazily - let exists = self.state.lock().unwrap().does_container_exist(&id); + let exists = self.state.lock_unpoisoned().does_container_exist(&id); if !exists { missing_containers.push(id); continue; } // Ensure registration so handlers can be created - self.state.lock().unwrap().ensure_container(&id); + self.state.lock_unpoisoned().ensure_container(&id); } - if skip_unreachable && !remapped && !self.state.lock().unwrap().get_reachable(&id) { + if skip_unreachable && !remapped && !self.state.lock_unpoisoned().get_reachable(&id) { continue; } @@ -1133,17 +1136,17 @@ impl LoroDoc { /// This is for debugging purpose. It will travel the whole oplog #[inline] pub fn diagnose_size(&self) { - self.oplog().lock().unwrap().diagnose_size(); + self.oplog().lock_unpoisoned().diagnose_size(); } #[inline] pub fn oplog_frontiers(&self) -> Frontiers { - self.oplog().lock().unwrap().frontiers().clone() + self.oplog().lock_unpoisoned().frontiers().clone() } #[inline] pub fn state_frontiers(&self) -> Frontiers { - self.state.lock().unwrap().frontiers.clone() + self.state.lock_unpoisoned().frontiers.clone() } /// - Ordering::Less means self is less than target or parallel @@ -1151,7 +1154,7 @@ impl LoroDoc { /// - Ordering::Greater means self's version is greater than target #[inline] pub fn cmp_with_frontiers(&self, other: &Frontiers) -> Ordering { - self.oplog().lock().unwrap().cmp_with_frontiers(other) + self.oplog().lock_unpoisoned().cmp_with_frontiers(other) } /// Compare two [Frontiers] causally. @@ -1163,11 +1166,11 @@ impl LoroDoc { a: &Frontiers, b: &Frontiers, ) -> Result, FrontiersNotIncluded> { - self.oplog().lock().unwrap().cmp_frontiers(a, b) + self.oplog().lock_unpoisoned().cmp_frontiers(a, b) } pub fn subscribe_root(&self, callback: Subscriber) -> Subscription { - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); if !state.is_recording() { state.start_recording(); } @@ -1176,7 +1179,7 @@ impl LoroDoc { } pub fn subscribe(&self, container_id: &ContainerID, callback: Subscriber) -> Subscription { - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); if !state.is_recording() { state.start_recording(); } @@ -1239,7 +1242,7 @@ impl LoroDoc { // operation atomic with respect to local edits. let is_detached = self.is_detached(); self.set_detached(true); - self.oplog.lock().unwrap().batch_importing = true; + self.oplog.lock_unpoisoned().batch_importing = true; let mut err = None; for (_meta, data) in meta_arr { match self._import_with(data, Default::default()) { @@ -1275,7 +1278,7 @@ impl LoroDoc { } } - let mut oplog = self.oplog.lock().unwrap(); + let mut oplog = self.oplog.lock_unpoisoned(); oplog.batch_importing = false; drop(oplog); if !is_detached { @@ -1302,19 +1305,19 @@ impl LoroDoc { /// Get shallow value of the document. #[inline] pub fn get_value(&self) -> LoroValue { - self.state.lock().unwrap().get_value() + self.state.lock_unpoisoned().get_value() } /// Get deep value of the document. #[inline] pub fn get_deep_value(&self) -> LoroValue { - self.state.lock().unwrap().get_deep_value() + self.state.lock_unpoisoned().get_deep_value() } /// Get deep value of the document with container id #[inline] pub fn get_deep_value_with_id(&self) -> LoroValue { - self.state.lock().unwrap().get_deep_value_with_id() + self.state.lock_unpoisoned().get_deep_value_with_id() } pub fn checkout_to_latest(&self) { @@ -1472,12 +1475,12 @@ impl LoroDoc { #[inline] pub fn vv_to_frontiers(&self, vv: &VersionVector) -> Frontiers { - self.oplog.lock().unwrap().dag.vv_to_frontiers(vv) + self.oplog.lock_unpoisoned().dag.vv_to_frontiers(vv) } #[inline] pub fn frontiers_to_vv(&self, frontiers: &Frontiers) -> Option { - self.oplog.lock().unwrap().dag.frontiers_to_vv(frontiers) + self.oplog.lock_unpoisoned().dag.frontiers_to_vv(frontiers) } /// Import ops from other doc. @@ -1494,7 +1497,7 @@ impl LoroDoc { #[inline] pub fn len_ops(&self) -> usize { - let oplog = self.oplog.lock().unwrap(); + let oplog = self.oplog.lock_unpoisoned(); let ans = oplog.vv().values().sum::() as usize; if oplog.is_shallow() { let sub = oplog @@ -1510,7 +1513,7 @@ impl LoroDoc { #[inline] pub fn len_changes(&self) -> usize { - let oplog = self.oplog.lock().unwrap(); + let oplog = self.oplog.lock_unpoisoned(); oplog.len_changes() } @@ -1536,7 +1539,7 @@ impl LoroDoc { let s = info_span!("CheckStateDiffCalcConsistencySlow", ?peer_id); let _g = s.enter(); let options = self.implicit_commit_then_stop().0; - self.oplog.lock().unwrap().check_dag_correctness(); + self.oplog.lock_unpoisoned().check_dag_correctness(); if self.is_shallow() { // For shallow documents, we cannot replay from the beginning as the history is not complete. // @@ -1570,8 +1573,8 @@ impl LoroDoc { // Step 5: Checkout to the current state's frontiers and compare the states. // doc.checkout(&self.state_frontiers()).unwrap(); assert_eq!(doc.get_deep_value(), self.get_deep_value()); - let mut calculated_state = doc.app_state().lock().unwrap(); - let mut current_state = self.app_state().lock().unwrap(); + let mut calculated_state = doc.app_state().lock_unpoisoned(); + let mut current_state = self.app_state().lock_unpoisoned(); current_state.check_is_the_same(&mut calculated_state); } else { let f = self.state_frontiers(); @@ -1585,8 +1588,8 @@ impl LoroDoc { let bytes = self.export(ExportMode::updates_till(&vv)).unwrap(); let doc = Self::new(); doc.import(&bytes).unwrap(); - let mut calculated_state = doc.app_state().lock().unwrap(); - let mut current_state = self.app_state().lock().unwrap(); + let mut calculated_state = doc.app_state().lock_unpoisoned(); + let mut current_state = self.app_state().lock_unpoisoned(); current_state.check_is_the_same(&mut calculated_state); } @@ -1609,7 +1612,7 @@ impl LoroDoc { return Err(CannotFindRelativePosition::IdNotFound); } - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); if let Some(ans) = state.get_relative_position(pos, ret_event_index) { Ok(PosQueryResult { update: None, @@ -1632,12 +1635,12 @@ impl LoroDoc { // commit the txn to make sure we can query the history correctly, preserving options drop(state); let result = self.with_barrier(|| { - let oplog = self.oplog().lock().unwrap(); + let oplog = self.oplog().lock_unpoisoned(); // TODO: assert pos.id is not unknown if let Some(id) = pos.id { // Ensure the container is registered if it exists lazily if oplog.arena.id_to_idx(&pos.container).is_none() { - let mut s = self.state.lock().unwrap(); + let mut s = self.state.lock_unpoisoned(); if !s.does_container_exist(&pos.container) { return Err(CannotFindRelativePosition::ContainerDeleted); } @@ -1779,18 +1782,18 @@ impl LoroDoc { /// If you use checkout that switching to an old/concurrent version, the history cache will be built. /// You can free it by calling this method. pub fn free_history_cache(&self) { - self.oplog.lock().unwrap().free_history_cache(); + self.oplog.lock_unpoisoned().free_history_cache(); } /// Free the cached diff calculator that is used for checkout. pub fn free_diff_calculator(&self) { - *self.diff_calculator.lock().unwrap() = DiffCalculator::new(true); + *self.diff_calculator.lock_unpoisoned() = DiffCalculator::new(true); } /// If you use checkout that switching to an old/concurrent version, the history cache will be built. /// You can free it by calling `free_history_cache`. pub fn has_history_cache(&self) -> bool { - self.oplog.lock().unwrap().has_history_cache() + self.oplog.lock_unpoisoned().has_history_cache() } /// Encoded all ops and history cache to bytes and store them in the kv store. @@ -1799,7 +1802,7 @@ impl LoroDoc { #[inline] pub fn compact_change_store(&self) { self.with_barrier(|| { - self.oplog.lock().unwrap().compact_change_store(); + self.oplog.lock_unpoisoned().compact_change_store(); }); } @@ -1813,7 +1816,7 @@ impl LoroDoc { /// Get the path from the root to the container pub fn get_path_to_container(&self, id: &ContainerID) -> Option> { - let mut state = self.state.lock().unwrap(); + let mut state = self.state.lock_unpoisoned(); if state.arena.id_to_idx(id).is_none() { if !state.does_container_exist(id) { return None; @@ -1831,7 +1834,7 @@ impl LoroDoc { ExportMode::Snapshot => export_fast_snapshot(self), ExportMode::Updates { from } => export_fast_updates(self, &from), ExportMode::UpdatesInRange { spans } => { - export_fast_updates_in_range(&self.oplog.lock().unwrap(), spans.as_ref()) + export_fast_updates_in_range(&self.oplog.lock_unpoisoned(), spans.as_ref()) } ExportMode::ShallowSnapshot(f) => export_shallow_snapshot(self, &f)?, ExportMode::StateOnly(f) => match f { @@ -1850,7 +1853,7 @@ impl LoroDoc { /// /// The ops included by the shallow history start version vector are not in the doc. pub fn shallow_since_vv(&self) -> ImVersionVector { - self.oplog().lock().unwrap().shallow_since_vv().clone() + self.oplog().lock_unpoisoned().shallow_since_vv().clone() } pub fn shallow_since_frontiers(&self) -> Frontiers { @@ -1863,7 +1866,7 @@ impl LoroDoc { /// Check if the doc contains the full history. pub fn is_shallow(&self) -> bool { - !self.oplog().lock().unwrap().shallow_since_vv().is_empty() + !self.oplog().lock_unpoisoned().shallow_since_vv().is_empty() } /// Get the number of operations in the pending transaction. @@ -1871,7 +1874,7 @@ impl LoroDoc { /// The pending transaction is the one that is not committed yet. It will be committed /// after calling `doc.commit()`, `doc.export(mode)` or `doc.checkout(version)`. pub fn get_pending_txn_len(&self) -> usize { - if let Some(txn) = self.txn.lock().unwrap().as_ref() { + if let Some(txn) = self.txn.lock_unpoisoned().as_ref() { txn.len() } else { 0 @@ -1880,7 +1883,7 @@ impl LoroDoc { #[inline] pub fn find_id_spans_between(&self, from: &Frontiers, to: &Frontiers) -> VersionVectorDiff { - self.oplog().lock().unwrap().dag.find_path(from, to) + self.oplog().lock_unpoisoned().dag.find_path(from, to) } /// Subscribe to the first commit from a peer. Operations performed on the `LoroDoc` within this callback @@ -1951,7 +1954,7 @@ impl LoroDoc { } for id in ids { - let op_log = &self.oplog().lock().unwrap(); + let op_log = &self.oplog().lock_unpoisoned(); if !op_log.vv().includes_id(*id) { return Err(ChangeTravelError::TargetIdNotFound(*id)); } @@ -1964,7 +1967,7 @@ impl LoroDoc { let mut pending: BinaryHeap = BinaryHeap::new(); for id in ids { pending.push(PendingNode(ChangeMeta::from_change( - &self.oplog().lock().unwrap().get_change_at(*id).unwrap(), + &self.oplog().lock_unpoisoned().get_change_at(*id).unwrap(), ))); } while let Some(PendingNode(node)) = pending.pop() { @@ -1974,7 +1977,7 @@ impl LoroDoc { } for dep in deps.iter() { - let Some(dep_node) = self.oplog().lock().unwrap().get_change_at(dep) else { + let Some(dep_node) = self.oplog().lock_unpoisoned().get_change_at(dep) else { continue; }; if visited.contains(&dep_node.id) { @@ -1995,7 +1998,7 @@ impl LoroDoc { self.with_barrier(|| { let mut set = FxHashSet::default(); { - let oplog = self.oplog().lock().unwrap(); + let oplog = self.oplog().lock_unpoisoned(); for op in oplog.iter_ops(id.to_span(len)) { let id = oplog.arena.get_container_id(op.container()).unwrap(); set.insert(id); @@ -2068,7 +2071,7 @@ pub struct CommitWhenDrop<'a> { impl Drop for CommitWhenDrop<'_> { fn drop(&mut self) { { - let mut guard = self.doc.txn.lock().unwrap(); + let mut guard = self.doc.txn.lock_unpoisoned(); if let Some(txn) = guard.as_mut() { txn.set_default_options(std::mem::take(&mut self.default_options)); }; @@ -2230,7 +2233,7 @@ mod test { .insert(0, "hello", PosType::Unicode) .unwrap(); b.commit_then_renew(); - let oplog = b.oplog().lock().unwrap(); + let oplog = b.oplog().lock_unpoisoned(); drop(oplog); b.export(ExportMode::all_updates()).unwrap(); } @@ -2240,7 +2243,7 @@ mod test { let doc = LoroDoc::new(); let oplog = doc.oplog.clone(); let _ = std::panic::catch_unwind(AssertUnwindSafe(|| { - let _guard = oplog.lock().unwrap(); + let _guard = oplog.lock_unpoisoned(); panic!("poison oplog"); })); diff --git a/crates/loro-internal/src/oplog.rs b/crates/loro-internal/src/oplog.rs index a85a67e18..7ff8d3324 100644 --- a/crates/loro-internal/src/oplog.rs +++ b/crates/loro-internal/src/oplog.rs @@ -3,6 +3,7 @@ pub(crate) mod loro_dag; mod pending_changes; use crate::sync::Mutex; +use crate::sync::MutexExt as _; use bytes::Bytes; use std::borrow::Cow; use std::cell::RefCell; @@ -149,16 +150,16 @@ impl OpLog { where F: FnOnce(&mut ContainerHistoryCache) -> R, { - let mut history_cache = self.history_cache.lock().unwrap(); + let mut history_cache = self.history_cache.lock_unpoisoned(); f(&mut history_cache) } pub fn has_history_cache(&self) -> bool { - self.history_cache.lock().unwrap().has_cache() + self.history_cache.lock_unpoisoned().has_cache() } pub fn free_history_cache(&self) { - let mut history_cache = self.history_cache.lock().unwrap(); + let mut history_cache = self.history_cache.lock_unpoisoned(); history_cache.free(); } diff --git a/crates/loro-internal/src/oplog/change_store.rs b/crates/loro-internal/src/oplog/change_store.rs index b79fe6550..5da820c79 100644 --- a/crates/loro-internal/src/oplog/change_store.rs +++ b/crates/loro-internal/src/oplog/change_store.rs @@ -1,6 +1,7 @@ use self::block_encode::{decode_block, decode_header, encode_block, ChangesBlockHeader}; use super::{loro_dag::AppDagNodeInner, AppDagNode}; use crate::sync::Mutex; +use crate::sync::MutexExt as _; use crate::{ arena::SharedArena, change::Change, @@ -135,7 +136,7 @@ impl ChangeStore { pub(super) fn encode_all(&self, vv: &VersionVector, frontiers: &Frontiers) -> Bytes { self.flush_and_compact(vv, frontiers); - let mut kv = self.external_kv.lock().unwrap(); + let mut kv = self.external_kv.lock_unpoisoned(); kv.export_all() } @@ -206,15 +207,15 @@ impl ChangeStore { latest_frontiers: &Frontiers, ) -> Bytes { { - let mut store = self.external_kv.lock().unwrap(); + let mut store = self.external_kv.lock_unpoisoned(); store.set(START_VV_KEY, start_vv.encode().into()); store.set(START_FRONTIERS_KEY, start_frontiers.encode().into()); - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); inner.start_frontiers = start_frontiers.clone(); inner.start_vv = ImVersionVector::from_vv(start_vv); } self.flush_and_compact(latest_vv, latest_frontiers); - self.external_kv.lock().unwrap().export_all() + self.external_kv.lock_unpoisoned().export_all() } pub(crate) fn decode_snapshot_for_updates( @@ -278,7 +279,7 @@ impl ChangeStore { pub fn visit_all_changes(&self, f: &mut dyn FnMut(&Change)) { self.ensure_block_loaded_in_range(Bound::Unbounded, Bound::Unbounded); - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); for (_, block) in inner.mem_parsed_kv.iter_mut() { block .ensure_changes(&self.arena) @@ -299,7 +300,7 @@ impl ChangeStore { Bound::Included(id_span.id_start()), Bound::Excluded(id_span.id_end()), ); - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); let next_back = inner.mem_parsed_kv.range(..=id_span.id_start()).next_back(); match next_back { None => { @@ -392,7 +393,7 @@ impl ChangeStore { } pub(crate) fn get_blocks_in_range(&self, id_span: IdSpan) -> VecDeque> { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); let start_counter = inner .mem_parsed_kv .range(..=id_span.id_start()) @@ -421,7 +422,7 @@ impl ChangeStore { pub(crate) fn get_block_that_contains(&self, id: ID) -> Option> { self.ensure_block_loaded_in_range(Bound::Included(id), Bound::Included(id)); - let inner = self.inner.lock().unwrap(); + let inner = self.inner.lock_unpoisoned(); let block = inner .mem_parsed_kv .range(..=id) @@ -439,7 +440,7 @@ impl ChangeStore { pub(crate) fn get_the_last_block_of_peer(&self, peer: PeerID) -> Option> { let end_id = ID::new(peer, Counter::MAX); self.ensure_id_lte(end_id); - let inner = self.inner.lock().unwrap(); + let inner = self.inner.lock_unpoisoned(); let block = inner .mem_parsed_kv .range(..=end_id) @@ -452,7 +453,7 @@ impl ChangeStore { pub fn change_num(&self) -> usize { self.ensure_block_loaded_in_range(Bound::Unbounded, Bound::Unbounded); - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); inner .mem_parsed_kv .iter_mut() @@ -468,7 +469,7 @@ impl ChangeStore { frontiers: &Frontiers, ) -> Self { self.flush_and_compact(vv, frontiers); - let inner = self.inner.lock().unwrap(); + let inner = self.inner.lock_unpoisoned(); Self { inner: Arc::new(Mutex::new(ChangeStoreInner { start_vv: inner.start_vv.clone(), @@ -476,8 +477,8 @@ impl ChangeStore { mem_parsed_kv: BTreeMap::new(), })), arena, - external_vv: Arc::new(Mutex::new(self.external_vv.lock().unwrap().clone())), - external_kv: self.external_kv.lock().unwrap().clone_store(), + external_vv: Arc::new(Mutex::new(self.external_vv.lock_unpoisoned().clone())), + external_kv: self.external_kv.lock_unpoisoned().clone_store(), merge_interval, } } @@ -567,7 +568,7 @@ fn encode_blocks_in_store( arena: &SharedArena, w: &mut W, ) { - let mut inner = new_store.inner.lock().unwrap(); + let mut inner = new_store.inner.lock_unpoisoned(); for (_id, block) in inner.mem_parsed_kv.iter_mut() { let bytes = block.to_bytes(arena); leb128::write::unsigned(w, bytes.bytes.len() as u64).unwrap(); @@ -583,7 +584,7 @@ mod mut_external_kv { impl ChangeStore { #[tracing::instrument(skip_all, level = "debug", name = "change_store import_all")] pub(crate) fn import_all(&self, bytes: Bytes) -> Result { - let mut kv_store = self.external_kv.lock().unwrap(); + let mut kv_store = self.external_kv.lock_unpoisoned(); assert!( // 2 because there are vv and frontiers kv_store.len() <= 2, @@ -608,10 +609,10 @@ mod mut_external_kv { for (peer, cnt) in vv.iter() { self.get_change(ID::new(*peer, *cnt - 1)).unwrap(); } - kv_store = self.external_kv.lock().unwrap(); + kv_store = self.external_kv.lock_unpoisoned(); } - *self.external_vv.lock().unwrap() = vv.clone(); + *self.external_vv.lock_unpoisoned() = vv.clone(); let frontiers_bytes = kv_store.get(FRONTIERS_KEY).unwrap_or_default(); let frontiers = Frontiers::decode(&frontiers_bytes).unwrap(); let start_frontiers = kv_store.get(START_FRONTIERS_KEY).unwrap_or_default(); @@ -648,7 +649,7 @@ mod mut_external_kv { start_version: if start_vv.is_empty() { None } else { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); inner.start_frontiers = start_frontiers.clone(); inner.start_vv = ImVersionVector::from_vv(&start_vv); Some((start_vv, start_frontiers)) @@ -658,9 +659,9 @@ mod mut_external_kv { /// Flush the cached change to kv_store pub(crate) fn flush_and_compact(&self, vv: &VersionVector, frontiers: &Frontiers) { - let mut inner = self.inner.lock().unwrap(); - let mut store = self.external_kv.lock().unwrap(); - let mut external_vv = self.external_vv.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); + let mut store = self.external_kv.lock_unpoisoned(); + let mut external_vv = self.external_vv.lock_unpoisoned(); for (id, block) in inner.mem_parsed_kv.iter_mut() { if !block.flushed { let id_bytes = id.to_bytes(); @@ -711,7 +712,7 @@ mod mut_inner_kv { pub fn insert_change(&self, mut change: Change, split_when_exceeds: bool, is_local: bool) { #[cfg(debug_assertions)] { - let vv = self.external_vv.lock().unwrap(); + let vv = self.external_vv.lock_unpoisoned(); assert!(vv.get(&change.id.peer).copied().unwrap_or(0) <= change.id.counter); } @@ -724,7 +725,7 @@ mod mut_inner_kv { } let id = change.id; - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); // try to merge with previous block if let Some((_id, block)) = inner.mem_parsed_kv.range_mut(..id).next_back() { @@ -777,7 +778,7 @@ mod mut_inner_kv { pub fn get_change_by_lamport_lte(&self, idlp: IdLp) -> Option { // This method is complicated because we impl binary search on top of the range api // It can be simplified - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); let mut iter = inner .mem_parsed_kv .range_mut(ID::new(idlp.peer, 0)..ID::new(idlp.peer, i32::MAX)); @@ -878,7 +879,7 @@ mod mut_inner_kv { let scan_end = ID::new(idlp.peer, counter_end).to_bytes(); let (id, bytes) = 'block_scan: { - let kv_store = &self.external_kv.lock().unwrap(); + let kv_store = &self.external_kv.lock_unpoisoned(); let iter = kv_store .scan( Bound::Included(&ID::new(idlp.peer, 0).to_bytes()), @@ -1000,7 +1001,7 @@ mod mut_inner_kv { } fn get_parsed_block(&self, id: ID) -> Option> { - let mut inner = self.inner.lock().unwrap(); + let mut inner = self.inner.lock_unpoisoned(); if let Some((_id, block)) = inner.mem_parsed_kv.range_mut(..=id).next_back() { if block.peer == id.peer && block.counter_range.1 > id.counter { block @@ -1010,7 +1011,7 @@ mod mut_inner_kv { } } - let store = self.external_kv.lock().unwrap(); + let store = self.external_kv.lock_unpoisoned(); let mut iter = store .scan(Bound::Unbounded, Bound::Included(&id.to_bytes())) .filter(|(id, _)| id.len() == 12); @@ -1057,8 +1058,8 @@ mod mut_inner_kv { { let start = start.map(|id| id.to_bytes()); let end = end.map(|id| id.to_bytes()); - let kv = self.external_kv.lock().unwrap(); - let mut inner = self.inner.lock().unwrap(); + let kv = self.external_kv.lock_unpoisoned(); + let mut inner = self.inner.lock_unpoisoned(); for (id, bytes) in kv .scan( start.as_ref().map(|x| x.as_slice()), @@ -1088,8 +1089,8 @@ mod mut_inner_kv { } pub(super) fn ensure_id_lte(&self, id: ID) { - let kv = self.external_kv.lock().unwrap(); - let mut inner = self.inner.lock().unwrap(); + let kv = self.external_kv.lock_unpoisoned(); + let mut inner = self.inner.lock_unpoisoned(); let Some((next_back_id, next_back_bytes)) = kv .scan(Bound::Unbounded, Bound::Included(&id.to_bytes())) .filter(|(id, _)| id.len() == 12) @@ -1560,13 +1561,13 @@ mod test { fn test_encode_decode(doc: LoroDoc) { doc.commit_then_renew(); - let oplog = doc.oplog().lock().unwrap(); + let oplog = doc.oplog().lock_unpoisoned(); let bytes = oplog .change_store .encode_all(oplog.vv(), oplog.dag.frontiers()); let store = ChangeStore::new_for_test(); let _ = store.import_all(bytes.clone()).unwrap(); - assert_eq!(store.external_kv.lock().unwrap().export_all(), bytes); + assert_eq!(store.external_kv.lock_unpoisoned().export_all(), bytes); let mut changes_parsed = Vec::new(); let a = store.arena.clone(); store.visit_all_changes(&mut |c| { diff --git a/crates/loro-internal/src/oplog/loro_dag.rs b/crates/loro-internal/src/oplog/loro_dag.rs index 0b5595678..2be24f1e3 100644 --- a/crates/loro-internal/src/oplog/loro_dag.rs +++ b/crates/loro-internal/src/oplog/loro_dag.rs @@ -3,6 +3,7 @@ use crate::dag::{Dag, DagNode}; use crate::id::{Counter, ID}; use crate::span::{HasId, HasLamport}; use crate::sync::Mutex; +use crate::sync::MutexExt as _; use crate::version::{shrink_frontiers, Frontiers, ImVersionVector, VersionVector}; use loro_common::{HasCounter, HasCounterSpan, HasIdSpan, HasLamportSpan, PeerID}; use once_cell::sync::OnceCell; @@ -137,7 +138,7 @@ impl AppDag { self.update_version_on_new_change(change, from_local); #[cfg(debug_assertions)] { - let unhandled_dep_points = self.unhandled_dep_points.lock().unwrap(); + let unhandled_dep_points = self.unhandled_dep_points.lock_unpoisoned(); let c = unhandled_dep_points .range(change.id_start()..change.id_end()) .count(); @@ -184,7 +185,7 @@ impl AppDag { } .into(); - let mut map = self.map.lock().unwrap(); + let mut map = self.map.lock_unpoisoned(); map.insert(node.id_start(), node); self.handle_deps_break_points(change.deps.iter(), change.id.peer, Some(&mut map)); } @@ -240,7 +241,7 @@ impl AppDag { f: impl FnOnce(Option<&mut AppDagNode>) -> R, ) -> R { self.lazy_load_last_of_peer(peer); - let mut binding = self.map.lock().unwrap(); + let mut binding = self.map.lock_unpoisoned(); let last = binding .range_mut(..=ID::new(peer, Counter::MAX)) .next_back() @@ -269,7 +270,7 @@ impl AppDag { } pub(super) fn lazy_load_last_of_peer(&mut self, peer: u64) { - let unparsed_vv = self.unparsed_vv.lock().unwrap(); + let unparsed_vv = self.unparsed_vv.lock_unpoisoned(); if !unparsed_vv.contains_key(&peer) || self.vv[&peer] >= unparsed_vv[&peer] { return; } @@ -290,16 +291,16 @@ impl AppDag { assert!(!nodes.is_empty()); let mut map_guard = None; let map = map_input.unwrap_or_else(|| { - map_guard = Some(self.map.lock().unwrap()); + map_guard = Some(self.map.lock_unpoisoned()); map_guard.as_mut().unwrap() }); let new_dag_start_counter_for_the_peer = nodes[0].cnt; let nodes_cnt_end = nodes.last().unwrap().ctr_end(); - let mut unparsed_vv = self.unparsed_vv.lock().unwrap(); + let mut unparsed_vv = self.unparsed_vv.lock_unpoisoned(); let end_counter = unparsed_vv[&peer]; assert!(end_counter <= nodes_cnt_end); let mut deps_on_others = Vec::new(); - let mut break_point_set = self.unhandled_dep_points.lock().unwrap(); + let mut break_point_set = self.unhandled_dep_points.lock_unpoisoned(); for mut node in nodes { if node.cnt >= end_counter { // skip already parsed nodes @@ -364,7 +365,7 @@ impl AppDag { ) { let mut map_guard = None; let map = map.unwrap_or_else(|| { - map_guard = Some(self.map.lock().unwrap()); + map_guard = Some(self.map.lock_unpoisoned()); map_guard.as_mut().unwrap() }); for id in ids { @@ -397,7 +398,7 @@ impl AppDag { if let Some(new_node) = ans { map.insert(new_node.id_start(), new_node); } else if !handled { - self.unhandled_dep_points.lock().unwrap().insert(id); + self.unhandled_dep_points.lock_unpoisoned().insert(id); } } } @@ -411,7 +412,7 @@ impl AppDag { // We need to load all the dag nodes that has the same peer and greater counter than the given `id` // Because we only record the end counter of the unparsed version on `unparsed_vv` let unparsed_end = { - let unparsed_vv = self.unparsed_vv.lock().unwrap(); + let unparsed_vv = self.unparsed_vv.lock_unpoisoned(); unparsed_vv.get(&id.peer).copied().unwrap_or(0) }; if unparsed_end <= id.counter { @@ -431,12 +432,12 @@ impl AppDag { } pub fn total_parsed_dag_node(&self) -> usize { - self.map.lock().unwrap().len() + self.map.lock_unpoisoned().len() } pub(crate) fn set_version_by_fast_snapshot_import(&mut self, v: BatchDecodeInfo) { assert!(self.vv.is_empty()); - *self.unparsed_vv.lock().unwrap() = v.vv.clone(); + *self.unparsed_vv.lock_unpoisoned() = v.vv.clone(); self.vv = v.vv; self.frontiers = v.frontiers; if let Some((vv, f)) = v.start_version { @@ -465,7 +466,7 @@ impl AppDag { pub fn check_dag_correctness(&self) { { // parse all nodes - let unparsed_vv = self.unparsed_vv.lock().unwrap().clone(); + let unparsed_vv = self.unparsed_vv.lock_unpoisoned().clone(); for (peer, cnt) in unparsed_vv.iter() { if *cnt == 0 { continue; @@ -486,11 +487,11 @@ impl AppDag { } } - self.unparsed_vv.lock().unwrap().clear(); + self.unparsed_vv.lock_unpoisoned().clear(); } { // check property 1: Counter is continuous - let map = self.map.lock().unwrap(); + let map = self.map.lock_unpoisoned(); let mut last_end_id = ID::new(0, 0); for (&id, node) in map.iter() { let init_counter = self.shallow_since_vv.get(&id.peer).copied().unwrap_or(0); @@ -505,12 +506,12 @@ impl AppDag { } { // check property 2: A node always depends of the last ids of other nodes - let map = self.map.lock().unwrap(); + let map = self.map.lock_unpoisoned(); check_always_dep_on_last_id(&map); } { // check property 3: Lamport is correctly calculated - let map = self.map.lock().unwrap(); + let map = self.map.lock_unpoisoned(); 'outer: for (_, node) in map.iter() { let mut this_lamport = 0; for dep in node.deps.iter() { @@ -527,7 +528,7 @@ impl AppDag { } { // check property 4: VV for each node is correctly calculated - let map = self.map.lock().unwrap().clone(); + let map = self.map.lock_unpoisoned().clone(); 'outer: for (_, node) in map.iter() { let actual_vv = self.ensure_vv_for(node); let mut expected_vv = ImVersionVector::default(); @@ -548,7 +549,7 @@ impl AppDag { { // check property 5: Frontiers are correctly calculated let mut maybe_frontiers = FxHashSet::default(); - let map = self.map.lock().unwrap(); + let map = self.map.lock_unpoisoned(); for (_, node) in map.iter() { maybe_frontiers.insert(node.id_last()); } @@ -824,7 +825,7 @@ impl Dag for AppDag { fn get(&self, id: ID) -> Option { self.ensure_lazy_load_node(id); - let binding = self.map.lock().unwrap(); + let binding = self.map.lock_unpoisoned(); if let Some(x) = binding.range(..=id).next_back() { if x.1.contains_id(id) { // PERF: do we need to optimize clone like this? diff --git a/crates/loro-internal/src/pre_commit.rs b/crates/loro-internal/src/pre_commit.rs index eb6f3fbe5..60ea9729a 100644 --- a/crates/loro-internal/src/pre_commit.rs +++ b/crates/loro-internal/src/pre_commit.rs @@ -1,5 +1,6 @@ use std::sync::{Arc, Mutex}; +use crate::sync::MutexExt as _; use crate::{ change::{Change, Timestamp}, oplog::get_timestamp_now_txn, @@ -41,22 +42,22 @@ struct ChangeModifierInner { impl ChangeModifier { pub fn set_message(&self, msg: &str) -> &Self { - self.0.lock().unwrap().new_msg = Some(Arc::from(msg)); + self.0.lock_unpoisoned().new_msg = Some(Arc::from(msg)); self } pub fn set_timestamp(&self, timestamp: Timestamp) -> &Self { - self.0.lock().unwrap().new_timestamp = Some(timestamp); + self.0.lock_unpoisoned().new_timestamp = Some(timestamp); self } pub fn set_timestamp_now(&self) -> &Self { - self.0.lock().unwrap().new_timestamp = Some(get_timestamp_now_txn()); + self.0.lock_unpoisoned().new_timestamp = Some(get_timestamp_now_txn()); self } pub(crate) fn modify_change(&self, change: &mut Change) { - let m = self.0.lock().unwrap(); + let m = self.0.lock_unpoisoned(); if let Some(msg) = &m.new_msg { change.commit_msg = Some(msg.clone()); } diff --git a/crates/loro-internal/src/state.rs b/crates/loro-internal/src/state.rs index 1b9bacf0b..9616effbb 100644 --- a/crates/loro-internal/src/state.rs +++ b/crates/loro-internal/src/state.rs @@ -1,3 +1,4 @@ +use crate::sync::MutexExt as _; use crate::sync::{AtomicU64, Mutex}; use std::sync::RwLock; use std::sync::{Arc, Weak}; @@ -896,7 +897,7 @@ impl DocState { let roots = self.arena.root_containers(flag); let mut ans = FxHashMap::with_capacity_and_hasher(roots.len(), Default::default()); let binding = self.config.deleted_root_containers.clone(); - let deleted_root_container = binding.lock().unwrap(); + let deleted_root_container = binding.lock_unpoisoned(); let should_hide_empty_root_container = self .config .hide_empty_root_containers diff --git a/crates/loro-internal/src/state/analyzer.rs b/crates/loro-internal/src/state/analyzer.rs index dae04c9d3..cd0979ab6 100644 --- a/crates/loro-internal/src/state/analyzer.rs +++ b/crates/loro-internal/src/state/analyzer.rs @@ -22,7 +22,7 @@ impl DocAnalysis { let mut ops_nums = FxHashMap::default(); let mut last_edit_time = FxHashMap::default(); { - let oplog = doc.oplog().lock().unwrap(); + let oplog = doc.oplog().lock_unpoisoned(); oplog.change_store().visit_all_changes(&mut |c| { for op in c.ops().iter() { let idx = op.container; @@ -38,7 +38,7 @@ impl DocAnalysis { } let mut containers = FxHashMap::default(); - let mut state = doc.app_state().lock().unwrap(); + let mut state = doc.app_state().lock_unpoisoned(); let alive_containers = state.get_all_alive_containers(); for (&idx, c) in state.iter_all_containers_mut() { let ops_num = ops_nums.get(&idx).unwrap_or(&0); diff --git a/crates/loro-internal/src/state/container_store.rs b/crates/loro-internal/src/state/container_store.rs index f37b09694..b8c53f03a 100644 --- a/crates/loro-internal/src/state/container_store.rs +++ b/crates/loro-internal/src/state/container_store.rs @@ -341,7 +341,7 @@ mod test { #[test] fn test_container_store_exports_imports() { let doc = init_doc(); - let mut s = doc.app_state().lock().unwrap(); + let mut s = doc.app_state().lock_unpoisoned(); let bytes = s.store.encode(); let mut new_store = decode_container_store(bytes); s.store.check_eq_after_parsing(&mut new_store); diff --git a/crates/loro-internal/src/state/container_store/inner_store.rs b/crates/loro-internal/src/state/container_store/inner_store.rs index 7fe01c665..03dea813e 100644 --- a/crates/loro-internal/src/state/container_store/inner_store.rs +++ b/crates/loro-internal/src/state/container_store/inner_store.rs @@ -1,3 +1,4 @@ +use crate::sync::MutexExt as _; use crate::{ arena::SharedArena, configure::Configure, container::idx::ContainerIdx, state::container_store::FRONTIERS_KEY, utils::kv_wrapper::KvWrapper, version::Frontiers, @@ -133,7 +134,7 @@ impl InnerStore { } pub(crate) fn flush(&mut self) { - let deleted = self.config.deleted_root_containers.lock().unwrap(); + let deleted = self.config.deleted_root_containers.lock_unpoisoned(); self.kv .set_all(self.store.iter_mut().filter_map(|(idx, c)| { if c.is_flushed() { diff --git a/crates/loro-internal/src/subscription.rs b/crates/loro-internal/src/subscription.rs index a7817273f..710a38d87 100644 --- a/crates/loro-internal/src/subscription.rs +++ b/crates/loro-internal/src/subscription.rs @@ -2,6 +2,7 @@ use super::{ arena::SharedArena, event::{DiffEvent, DocDiff}, }; +use crate::sync::MutexExt as _; use crate::{ container::idx::ContainerIdx, utils::subscription::SubscriberSet, ContainerDiff, LoroDoc, Subscription, @@ -79,10 +80,10 @@ impl Observer { pub(crate) fn emit(&self, doc_diff: DocDiff) { let success = self.emit_inner(doc_diff); if success { - let mut e = self.inner.queue.lock().unwrap().pop_front(); + let mut e = self.inner.queue.lock_unpoisoned().pop_front(); while let Some(event) = e { self.emit_inner(event); - e = self.inner.queue.lock().unwrap().pop_front(); + e = self.inner.queue.lock_unpoisoned().pop_front(); } } } @@ -113,7 +114,7 @@ impl Observer { .any(|x| inner.subscriber_set.is_recursive_calling(&Some(*x))) { drop(container_events_map); - inner.queue.lock().unwrap().push_back(doc_diff); + inner.queue.lock_unpoisoned().push_back(doc_diff); return false; } } diff --git a/crates/loro-internal/src/sync.rs b/crates/loro-internal/src/sync.rs index ace9d7255..f4332aabd 100644 --- a/crates/loro-internal/src/sync.rs +++ b/crates/loro-internal/src/sync.rs @@ -4,9 +4,13 @@ pub use loom::thread; pub use std::thread; #[cfg(loom)] -pub use loom::sync::{Mutex, MutexGuard, RwLock}; +pub use loom::sync::{ + LockResult, Mutex, MutexGuard, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, +}; #[cfg(not(loom))] -pub use std::sync::{Mutex, MutexGuard, RwLock}; +pub use std::sync::{ + LockResult, Mutex, MutexGuard, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, +}; #[cfg(loom)] pub use loom::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, AtomicU8, AtomicUsize}; @@ -18,6 +22,46 @@ pub(crate) use my_thread_local::ThreadLocal; #[cfg(not(loom))] pub(crate) use thread_local::ThreadLocal; +fn unpoison(result: LockResult) -> T { + result.unwrap_or_else(PoisonError::into_inner) +} + +pub(crate) trait MutexExt { + fn lock_unpoisoned(&self) -> MutexGuard<'_, T>; +} + +impl MutexExt for Mutex { + fn lock_unpoisoned(&self) -> MutexGuard<'_, T> { + unpoison(self.lock()) + } +} + +#[allow(dead_code)] +pub(crate) trait RwLockExt { + fn read_unpoisoned(&self) -> RwLockReadGuard<'_, T>; + fn write_unpoisoned(&self) -> RwLockWriteGuard<'_, T>; + fn into_inner_unpoisoned(self) -> T + where + Self: Sized; +} + +impl RwLockExt for RwLock { + fn read_unpoisoned(&self) -> RwLockReadGuard<'_, T> { + unpoison(self.read()) + } + + fn write_unpoisoned(&self) -> RwLockWriteGuard<'_, T> { + unpoison(self.write()) + } + + fn into_inner_unpoisoned(self) -> T + where + Self: Sized, + { + self.into_inner().unwrap_or_else(PoisonError::into_inner) + } +} + #[cfg(loom)] mod my_thread_local { use std::sync::Arc; @@ -39,7 +83,7 @@ mod my_thread_local { } pub fn get_or_default(&self) -> Arc { - let mut content = self.content.lock().unwrap(); + let mut content = self.content.lock_unpoisoned(); let v = content .entry(thread::current().id()) .or_insert_with(|| Arc::new(T::default())); @@ -55,3 +99,33 @@ mod my_thread_local { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mutex_lock_recovers_after_poison() { + let lock = Mutex::new(7); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let _guard = lock.lock_unpoisoned(); + panic!("poison mutex"); + })); + + assert_eq!(*lock.lock_unpoisoned(), 7); + } + + #[test] + fn rwlock_recovers_after_poison() { + let lock = RwLock::new(7); + let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + let mut guard = lock.write_unpoisoned(); + *guard = 9; + panic!("poison rwlock"); + })); + + assert_eq!(*lock.read_unpoisoned(), 9); + *lock.write_unpoisoned() = 11; + assert_eq!(lock.into_inner_unpoisoned(), 11); + } +} diff --git a/crates/loro-internal/src/txn.rs b/crates/loro-internal/src/txn.rs index 0cbc9166b..c63517911 100644 --- a/crates/loro-internal/src/txn.rs +++ b/crates/loro-internal/src/txn.rs @@ -70,7 +70,7 @@ impl crate::LoroDoc { let obs = self.observer.clone(); let local_update_subs_weak = self.local_update_subs.downgrade(); txn.set_on_commit(Box::new(move |state, oplog, id_span| { - let mut state = state.lock().unwrap(); + let mut state = state.lock_unpoisoned(); let events = state.take_events(); drop(state); for event in events { @@ -84,7 +84,7 @@ impl crate::LoroDoc { if let Some(local_update_subs) = local_update_subs_weak.upgrade() { if !local_update_subs.inner().is_empty() { let bytes = - { export_fast_updates_in_range(&oplog.lock().unwrap(), &[id_span]) }; + { export_fast_updates_in_range(&oplog.lock_unpoisoned(), &[id_span]) }; local_update_subs.emit(&(), bytes); } } @@ -96,7 +96,7 @@ impl crate::LoroDoc { pub fn start_auto_commit(&self) { self.auto_commit .store(true, std::sync::atomic::Ordering::Release); - let mut self_txn = self.txn.lock().unwrap(); + let mut self_txn = self.txn.lock_unpoisoned(); if self_txn.is_some() || !self.can_edit() { return; } @@ -108,7 +108,7 @@ impl crate::LoroDoc { #[inline] pub fn renew_txn_if_auto_commit(&self, options: Option) { if self.auto_commit.load(std::sync::atomic::Ordering::Acquire) && self.can_edit() { - let mut self_txn = self.txn.lock().unwrap(); + let mut self_txn = self.txn.lock_unpoisoned(); if self_txn.is_some() { return; } @@ -334,8 +334,8 @@ impl Transaction { } pub fn new_with_origin(doc: Arc, origin: InternalString) -> Self { - let oplog_lock = doc.oplog.lock().unwrap(); - let mut state_lock = doc.state.lock().unwrap(); + let oplog_lock = doc.oplog.lock_unpoisoned(); + let mut state_lock = doc.state.lock_unpoisoned(); if state_lock.is_in_txn() { panic!("Cannot start a transaction while another one is in progress"); } @@ -431,7 +431,7 @@ impl Transaction { }; self.finished = true; if self.local_ops.is_empty() { - let mut state = doc.state.lock().unwrap(); + let mut state = doc.state.lock_unpoisoned(); state.abort_txn(); return Ok(Some(self.take_options())); } @@ -445,7 +445,7 @@ impl Transaction { id: ID::new(self.peer, self.start_counter), timestamp: self.latest_timestamp.max( self.timestamp - .unwrap_or_else(|| doc.oplog.lock().unwrap().get_timestamp_for_next_txn()), + .unwrap_or_else(|| doc.oplog.lock_unpoisoned().get_timestamp_for_next_txn()), ), commit_msg: take(&mut self.msg), }; @@ -453,7 +453,7 @@ impl Transaction { let change_meta = ChangeMeta::from_change(&change); { // add change to uncommit field of oplog - let mut oplog = doc.oplog.lock().unwrap(); + let mut oplog = doc.oplog.lock_unpoisoned(); oplog.set_uncommitted_change(change); } @@ -467,8 +467,8 @@ impl Transaction { }, ); - let mut oplog = doc.oplog.lock().unwrap(); - let mut state = doc.state.lock().unwrap(); + let mut oplog = doc.oplog.lock_unpoisoned(); + let mut state = doc.state.lock_unpoisoned(); let mut change = oplog.uncommitted_change.take().unwrap(); modifier.modify_change(&mut change); @@ -570,8 +570,8 @@ impl Transaction { content, }; - let mut oplog = doc.oplog.lock().unwrap(); - let mut state = doc.state.lock().unwrap(); + let mut oplog = doc.oplog.lock_unpoisoned(); + let mut state = doc.state.lock_unpoisoned(); if state.is_deleted(container) { return Err(LoroError::ContainerDeleted { container: Box::new(state.arena.idx_to_id(container).unwrap()), diff --git a/crates/loro-internal/src/undo.rs b/crates/loro-internal/src/undo.rs index 52a1aaf31..fef898ba7 100644 --- a/crates/loro-internal/src/undo.rs +++ b/crates/loro-internal/src/undo.rs @@ -1,5 +1,6 @@ use std::{cell::RefCell, collections::VecDeque, sync::Arc}; +use crate::sync::MutexExt as _; use crate::sync::{AtomicU64, Mutex}; use either::Either; use loro_common::{ @@ -326,7 +327,7 @@ impl Stack { pub fn pop(&mut self) -> Option<(StackItem, Arc>)> { while self.stack.back().unwrap().0.is_empty() && self.stack.len() > 1 { let (_, diff) = self.stack.pop_back().unwrap(); - let diff = diff.lock().unwrap(); + let diff = diff.lock_unpoisoned(); if !diff.cid_to_events.is_empty() { self.stack .back_mut() @@ -340,7 +341,7 @@ impl Stack { if self.stack.len() == 1 && self.stack.back().unwrap().0.is_empty() { // If the stack is empty, we need to clear the remote diff - self.stack.back_mut().unwrap().1.lock().unwrap().clear(); + self.stack.back_mut().unwrap().1.lock_unpoisoned().clear(); return None; } @@ -364,7 +365,7 @@ impl Stack { group: Option<&UndoGroup>, ) { let last = self.stack.back_mut().unwrap(); - let last_remote_diff = last.1.lock().unwrap(); + let last_remote_diff = last.1.lock_unpoisoned(); // Check if the remote diff is disjoint with the current undo group let is_disjoint_group = group.is_some_and(|g| { @@ -413,7 +414,7 @@ impl Stack { } let remote_diff = &mut self.stack.back_mut().unwrap().1; - let mut remote_diff = remote_diff.lock().unwrap(); + let mut remote_diff = remote_diff.lock_unpoisoned(); for e in diff { if let Some(d) = remote_diff.cid_to_events.get_mut(&e.id) { d.compose_ref(&e.diff); @@ -431,7 +432,7 @@ impl Stack { return; } let remote_diff = &mut self.stack.back_mut().unwrap().1; - remote_diff.lock().unwrap().transform(diff, false); + remote_diff.lock_unpoisoned().transform(diff, false); } pub fn clear(&mut self) { @@ -823,13 +824,13 @@ impl UndoManager { { let inner = self.inner.clone(); // We need to clone this because otherwise will be applied to the same remote diff - let remote_change_clone = remote_diff.lock().unwrap().clone(); + let remote_change_clone = remote_diff.lock_unpoisoned().clone(); let commit = doc.undo_internal( IdSpan { peer: self.peer(), counter: span.span, }, - &mut self.container_remap.lock().unwrap(), + &mut self.container_remap.lock_unpoisoned(), Some(&remote_change_clone), &mut |diff| { info_span!("transform remote diff").in_scope(|| { @@ -851,9 +852,9 @@ impl UndoManager { // remote_diff is also transformed by it now (that's what we need). transform_cursor( cursor, - &remote_diff.lock().unwrap(), + &remote_diff.lock_unpoisoned(), doc, - &self.container_remap.lock().unwrap(), + &self.container_remap.lock_unpoisoned(), ); } diff --git a/crates/loro-internal/src/utils/kv_wrapper.rs b/crates/loro-internal/src/utils/kv_wrapper.rs index 1814f381a..b5878b5db 100644 --- a/crates/loro-internal/src/utils/kv_wrapper.rs +++ b/crates/loro-internal/src/utils/kv_wrapper.rs @@ -1,4 +1,5 @@ use crate::sync::Mutex; +use crate::sync::MutexExt as _; use bytes::Bytes; use loro_kv_store::{mem_store::MemKvConfig, MemKvStore}; use std::{collections::BTreeSet, ops::Bound, sync::Arc}; @@ -15,7 +16,7 @@ impl Clone for KvWrapper { /// Deep clone the inner kv store. fn clone(&self) -> Self { Self { - kv: self.kv.lock().unwrap().clone_store(), + kv: self.kv.lock_unpoisoned().clone_store(), } } } @@ -38,27 +39,27 @@ impl KvWrapper { } pub fn import(&self, bytes: Bytes) { - let mut kv = self.kv.lock().unwrap(); + let mut kv = self.kv.lock_unpoisoned(); kv.import_all(bytes).unwrap(); } pub fn export(&self) -> Bytes { - let mut kv = self.kv.lock().unwrap(); + let mut kv = self.kv.lock_unpoisoned(); kv.export_all() } pub fn get(&self, key: &[u8]) -> Option { - let kv = self.kv.lock().unwrap(); + let kv = self.kv.lock_unpoisoned(); kv.get(key) } pub fn with_kv(&self, f: impl FnOnce(&dyn KvStore) -> R) -> R { - let kv = self.kv.lock().unwrap(); + let kv = self.kv.lock_unpoisoned(); f(&*kv) } pub fn set_all(&self, iter: impl Iterator) { - let mut kv = self.kv.lock().unwrap(); + let mut kv = self.kv.lock_unpoisoned(); for (k, v) in iter { kv.set(&k, v); } @@ -66,12 +67,12 @@ impl KvWrapper { #[allow(unused)] pub(crate) fn contains_key(&self, key: &[u8]) -> bool { - self.kv.lock().unwrap().contains_key(key) + self.kv.lock_unpoisoned().contains_key(key) } pub(crate) fn remove_same(&self, old_kv: &KvWrapper) { - let other = old_kv.kv.lock().unwrap(); - let mut this = self.kv.lock().unwrap(); + let other = old_kv.kv.lock_unpoisoned(); + let mut this = self.kv.lock_unpoisoned(); for (k, v) in other.scan(Bound::Unbounded, Bound::Unbounded) { if this.get(&k) == Some(v) { this.remove(&k); @@ -80,12 +81,12 @@ impl KvWrapper { } pub(crate) fn remove(&self, k: &[u8]) -> Option { - self.kv.lock().unwrap().remove(k) + self.kv.lock_unpoisoned().remove(k) } /// Remove all keys not in the given set pub(crate) fn retain_keys(&self, keys: &BTreeSet>) { - let mut kv = self.kv.lock().unwrap(); + let mut kv = self.kv.lock_unpoisoned(); let mut to_remove = BTreeSet::new(); for (k, _) in kv.scan(Bound::Unbounded, Bound::Unbounded) { if !keys.contains(&*k) { @@ -99,10 +100,10 @@ impl KvWrapper { } pub(crate) fn insert(&self, k: &[u8], v: Bytes) { - self.kv.lock().unwrap().set(k, v); + self.kv.lock_unpoisoned().set(k, v); } pub(crate) fn is_empty(&self) -> bool { - self.kv.lock().unwrap().is_empty() + self.kv.lock_unpoisoned().is_empty() } } diff --git a/crates/loro-internal/src/utils/subscription.rs b/crates/loro-internal/src/utils/subscription.rs index 568258a1d..d9ed77ae4 100644 --- a/crates/loro-internal/src/utils/subscription.rs +++ b/crates/loro-internal/src/utils/subscription.rs @@ -227,6 +227,7 @@ Apache License END OF TERMS AND CONDITIONS */ +use crate::sync::MutexExt as _; use crate::sync::{thread, AtomicBool, Mutex}; use smallvec::SmallVec; use std::collections::{BTreeMap, BTreeSet}; @@ -294,7 +295,7 @@ where callback: Callback, ) -> (Subscription, impl FnOnce()) { let active = Arc::new(AtomicBool::new(false)); - let mut lock = self.0.lock().unwrap(); + let mut lock = self.0.lock_unpoisoned(); let subscriber_id = post_inc(&mut lock.next_subscriber_id); let this = Arc::downgrade(&self.0); let emitter_key_1 = emitter_key.clone(); @@ -304,7 +305,7 @@ where return; }; - let mut lock = this.lock().unwrap(); + let mut lock = this.lock_unpoisoned(); let Some(subscribers) = lock.subscribers.get_mut(&emitter_key) else { // remove was called with this emitter_key return; @@ -347,7 +348,7 @@ where #[allow(unused)] pub fn remove(&self, emitter: &EmitterKey) -> impl IntoIterator { - let mut lock = self.0.lock().unwrap(); + let mut lock = self.0.lock_unpoisoned(); let subscribers = lock.subscribers.remove(emitter); subscribers .and_then(|x| x.left().map(|s| s.into_values())) @@ -363,7 +364,7 @@ where } pub fn is_recursive_calling(&self, emitter: &EmitterKey) -> bool { - if let Some(Either::Right(thread_id)) = self.0.lock().unwrap().subscribers.get(emitter) { + if let Some(Either::Right(thread_id)) = self.0.lock_unpoisoned().subscribers.get(emitter) { *thread_id == thread::current().id() } else { false @@ -379,7 +380,7 @@ where ) -> Result<(), SubscriptionError> { let mut subscribers = { let inner = loop { - let mut subscriber_set_state = self.0.lock().unwrap(); + let mut subscriber_set_state = self.0.lock_unpoisoned(); let Some(set) = subscriber_set_state.subscribers.get_mut(emitter) else { return Ok(()); }; @@ -413,7 +414,7 @@ where } }); - let mut lock = self.0.lock().unwrap(); + let mut lock = self.0.lock_unpoisoned(); // Add any new subscribers that were added while invoking the callback. if let Some(Either::Left(new_subscribers)) = lock.subscribers.remove(emitter) { @@ -436,11 +437,11 @@ where } pub fn is_empty(&self) -> bool { - self.0.lock().unwrap().subscribers.is_empty() + self.0.lock_unpoisoned().subscribers.is_empty() } pub fn may_include(&self, emitter: &EmitterKey) -> bool { - self.0.lock().unwrap().subscribers.contains_key(emitter) + self.0.lock_unpoisoned().subscribers.contains_key(emitter) } } @@ -460,7 +461,7 @@ where Callback: 'static + Send + Sync, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let lock = self.0.lock().unwrap(); + let lock = self.0.lock_unpoisoned(); f.debug_struct("SubscriberSet") .field("subscriber_count", &lock.subscribers.len()) .field("dropped_subscribers_count", &lock.dropped_subscribers.len()) @@ -495,7 +496,7 @@ impl Subscription { /// are dropped pub fn detach(self) { if let Some(unsubscribe) = self.unsubscribe.upgrade() { - unsubscribe.lock().unwrap().take(); + unsubscribe.lock_unpoisoned().take(); } } @@ -509,7 +510,7 @@ impl Subscription { impl Drop for Subscription { fn drop(&mut self) { if let Some(unsubscribe) = self.unsubscribe.upgrade() { - let unsubscribe = unsubscribe.lock().unwrap().take(); + let unsubscribe = unsubscribe.lock_unpoisoned().take(); if let Some(unsubscribe) = unsubscribe { unsubscribe(); } @@ -523,7 +524,7 @@ struct InnerSubscription { impl Drop for InnerSubscription { fn drop(&mut self) { - self.unsubscribe.lock().unwrap().take(); + self.unsubscribe.lock_unpoisoned().take(); } } @@ -628,13 +629,13 @@ where .retain(key, &mut |callback| (callback)(&payload)); match result { Ok(_) => { - let mut queue = self.queue.lock().unwrap(); + let mut queue = self.queue.lock_unpoisoned(); if let Some(new_pending_events) = queue.remove(key) { pending_events.extend(new_pending_events); } } Err(SubscriptionError::CannotEmitEventDueToRecursiveCall) => { - let mut queue = self.queue.lock().unwrap(); + let mut queue = self.queue.lock_unpoisoned(); queue.entry(key.clone()).or_default().push(payload); } } From 3121a3037af59c4eee856e0fef1ad1475fae1eba Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 2 Apr 2026 16:32:59 +0800 Subject: [PATCH 6/8] Fail fast on poisoned core locks --- crates/loro-internal/src/handler.rs | 1 + .../src/jsonpath/jsonpath_impl.rs | 1 - crates/loro-internal/src/lock.rs | 27 ++++--- crates/loro-internal/src/loro.rs | 15 +++- crates/loro-internal/src/oplog.rs | 5 +- crates/loro-internal/src/sync.rs | 29 ++++---- crates/loro-internal/src/txn.rs | 74 +++++++++++++++---- 7 files changed, 104 insertions(+), 48 deletions(-) diff --git a/crates/loro-internal/src/handler.rs b/crates/loro-internal/src/handler.rs index 2378de7df..3189a74e6 100644 --- a/crates/loro-internal/src/handler.rs +++ b/crates/loro-internal/src/handler.rs @@ -4190,6 +4190,7 @@ pub mod counter { use loro_common::LoroResult; use crate::{ + sync::MutexExt as _, txn::{EventHint, Transaction}, HandlerTrait, }; diff --git a/crates/loro-internal/src/jsonpath/jsonpath_impl.rs b/crates/loro-internal/src/jsonpath/jsonpath_impl.rs index 919343979..d60e31619 100644 --- a/crates/loro-internal/src/jsonpath/jsonpath_impl.rs +++ b/crates/loro-internal/src/jsonpath/jsonpath_impl.rs @@ -5,7 +5,6 @@ use crate::jsonpath::ast::{ ComparisonOperator, FilterExpression, LogicalOperator, Segment, Selector, }; use crate::jsonpath::JSONPathParser; -use crate::sync::MutexExt as _; use crate::{HandlerTrait, LoroDoc}; use loro_common::{ContainerID, LoroValue}; use std::ops::ControlFlow; diff --git a/crates/loro-internal/src/lock.rs b/crates/loro-internal/src/lock.rs index 346674a26..a688c98a8 100644 --- a/crates/loro-internal/src/lock.rs +++ b/crates/loro-internal/src/lock.rs @@ -132,7 +132,9 @@ impl LoroMutex { pub fn lock(&self) -> Result, std::sync::PoisonError>> { let caller = Location::caller(); let v = self.currently_locked_in_this_thread.get_or_default(); - let last = *v.lock().unwrap_or_else(|e| e.into_inner()); + let last = *v + .lock() + .expect("poisoned lock-order tracking mutex"); let this = LockInfo { kind: self.kind, caller_location: Some(caller), @@ -144,10 +146,8 @@ impl LoroMutex { ); } - // A previous panic while holding the mutex should not permanently disable - // the document. Recover the inner value and let the caller decide what to do next. - let ans = self.lock.lock().unwrap_or_else(|e| e.into_inner()); - *v.lock().unwrap_or_else(|e| e.into_inner()) = this; + let ans = self.lock.lock()?; + *v.lock().expect("poisoned lock-order tracking mutex") = this; let ans = LoroMutexGuard { guard: ans, _inner: LoroMutexGuardInner { @@ -160,10 +160,7 @@ impl LoroMutex { } pub fn lock_unpoisoned(&self) -> LoroMutexGuard<'_, T> { - match self.lock() { - Ok(guard) => guard, - Err(_) => unreachable!("LoroMutex::lock recovers poisoned state before returning"), - } + self.lock().expect("poisoned LoroMutex") } /// Returns whether the mutex appears locked at this instant. @@ -236,7 +233,9 @@ impl<'a, T> LoroMutexGuard<'a, T> { impl Drop for LoroMutexGuardInner<'_, T> { fn drop(&mut self) { let cur = self.inner.currently_locked_in_this_thread.get_or_default(); - let current_lock_info = *cur.lock().unwrap_or_else(|e| e.into_inner()); + let current_lock_info = *cur + .lock() + .expect("poisoned lock-order tracking mutex"); if current_lock_info.kind != self.this.kind { let bt = Backtrace::capture(); eprintln!("Locking release order violation callstack:\n{}", bt); @@ -246,7 +245,7 @@ impl Drop for LoroMutexGuardInner<'_, T> { ); } - *cur.lock().unwrap_or_else(|e| e.into_inner()) = self.last; + *cur.lock().expect("poisoned lock-order tracking mutex") = self.last; } } @@ -365,7 +364,8 @@ mod tests { } #[test] - fn test_poisoned_lock_can_be_recovered() { + #[should_panic(expected = "poisoned LoroMutex")] + fn test_poisoned_lock_panics_after_unwind() { let group = LoroLockGroup::new(); let mutex = group.new_lock(42, LockKind::Txn); @@ -374,7 +374,6 @@ mod tests { panic!("poison the lock"); })); - let guard = mutex.lock_unpoisoned(); - assert_eq!(*guard, 42); + let _ = mutex.lock_unpoisoned(); } } diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index f2dde8c79..702ffff7c 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -2239,7 +2239,7 @@ mod test { } #[test] - fn poisoned_mutex_allows_follow_up_operations() { + fn poisoned_mutex_keeps_follow_up_operations_failed() { let doc = LoroDoc::new(); let oplog = doc.oplog.clone(); let _ = std::panic::catch_unwind(AssertUnwindSafe(|| { @@ -2247,8 +2247,15 @@ mod test { panic!("poison oplog"); })); - let vv = std::panic::catch_unwind(AssertUnwindSafe(|| doc.oplog_vv())) - .expect("poisoned lock should be recovered"); - assert!(vv.is_empty()); + let err = std::panic::catch_unwind(AssertUnwindSafe(|| doc.oplog_vv())) + .expect_err("poisoned lock should continue to fail fast"); + let msg = if let Some(msg) = err.downcast_ref::<&str>() { + (*msg).to_string() + } else if let Some(msg) = err.downcast_ref::() { + msg.clone() + } else { + String::new() + }; + assert!(msg.contains("poisoned LoroMutex"), "{msg}"); } } diff --git a/crates/loro-internal/src/oplog.rs b/crates/loro-internal/src/oplog.rs index 7ff8d3324..6d109d3d8 100644 --- a/crates/loro-internal/src/oplog.rs +++ b/crates/loro-internal/src/oplog.rs @@ -226,7 +226,10 @@ impl OpLog { let mut max_last_counter = -1; for dep in deps.iter() { - let dep_vv = self.dag.get_vv(dep).unwrap(); + let dep_vv = self + .dag + .get_vv(dep) + .ok_or(LoroError::FrontiersNotFound(dep))?; max_last_counter = max_last_counter.max(dep_vv.get(&peer).cloned().unwrap_or(0) - 1); } diff --git a/crates/loro-internal/src/sync.rs b/crates/loro-internal/src/sync.rs index f4332aabd..06c94397d 100644 --- a/crates/loro-internal/src/sync.rs +++ b/crates/loro-internal/src/sync.rs @@ -5,11 +5,11 @@ pub use std::thread; #[cfg(loom)] pub use loom::sync::{ - LockResult, Mutex, MutexGuard, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, + LockResult, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, }; #[cfg(not(loom))] pub use std::sync::{ - LockResult, Mutex, MutexGuard, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard, + LockResult, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, }; #[cfg(loom)] @@ -22,8 +22,8 @@ pub(crate) use my_thread_local::ThreadLocal; #[cfg(not(loom))] pub(crate) use thread_local::ThreadLocal; -fn unpoison(result: LockResult) -> T { - result.unwrap_or_else(PoisonError::into_inner) +fn expect_not_poisoned(result: LockResult, lock_kind: &str) -> T { + result.unwrap_or_else(|_| panic!("poisoned {lock_kind}")) } pub(crate) trait MutexExt { @@ -32,7 +32,7 @@ pub(crate) trait MutexExt { impl MutexExt for Mutex { fn lock_unpoisoned(&self) -> MutexGuard<'_, T> { - unpoison(self.lock()) + expect_not_poisoned(self.lock(), "mutex") } } @@ -47,18 +47,19 @@ pub(crate) trait RwLockExt { impl RwLockExt for RwLock { fn read_unpoisoned(&self) -> RwLockReadGuard<'_, T> { - unpoison(self.read()) + expect_not_poisoned(self.read(), "rwlock") } fn write_unpoisoned(&self) -> RwLockWriteGuard<'_, T> { - unpoison(self.write()) + expect_not_poisoned(self.write(), "rwlock") } fn into_inner_unpoisoned(self) -> T where Self: Sized, { - self.into_inner().unwrap_or_else(PoisonError::into_inner) + self.into_inner() + .unwrap_or_else(|_| panic!("poisoned rwlock")) } } @@ -105,18 +106,20 @@ mod tests { use super::*; #[test] - fn mutex_lock_recovers_after_poison() { + #[should_panic(expected = "poisoned mutex")] + fn mutex_lock_panics_after_poison() { let lock = Mutex::new(7); let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let _guard = lock.lock_unpoisoned(); panic!("poison mutex"); })); - assert_eq!(*lock.lock_unpoisoned(), 7); + drop(lock.lock_unpoisoned()); } #[test] - fn rwlock_recovers_after_poison() { + #[should_panic(expected = "poisoned rwlock")] + fn rwlock_read_panics_after_poison() { let lock = RwLock::new(7); let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { let mut guard = lock.write_unpoisoned(); @@ -124,8 +127,6 @@ mod tests { panic!("poison rwlock"); })); - assert_eq!(*lock.read_unpoisoned(), 9); - *lock.write_unpoisoned() = 11; - assert_eq!(lock.into_inner_unpoisoned(), 11); + drop(lock.read_unpoisoned()); } } diff --git a/crates/loro-internal/src/txn.rs b/crates/loro-internal/src/txn.rs index c63517911..c2c26cd77 100644 --- a/crates/loro-internal/src/txn.rs +++ b/crates/loro-internal/src/txn.rs @@ -1,4 +1,3 @@ -use core::panic; use std::{ borrow::Cow, mem::take, @@ -65,7 +64,7 @@ impl crate::LoroDoc { )); } - let mut txn = Transaction::new_with_origin(self.inner.clone(), origin.into()); + let mut txn = Transaction::new_with_origin(self.inner.clone(), origin.into())?; let obs = self.observer.clone(); let local_update_subs_weak = self.local_update_subs.downgrade(); @@ -101,7 +100,9 @@ impl crate::LoroDoc { return; } - let txn = self.txn().unwrap(); + let txn = self + .txn() + .expect("auto-commit should be able to create a transaction"); self_txn.replace(txn); } @@ -113,7 +114,9 @@ impl crate::LoroDoc { return; } - let mut txn = self.txn().unwrap(); + let mut txn = self + .txn() + .expect("auto-commit should be able to renew a transaction"); if let Some(options) = options { txn.set_options(options); } @@ -132,7 +135,9 @@ impl crate::LoroDoc { return; } - let mut txn = self.txn().unwrap(); + let mut txn = self + .txn() + .expect("auto-commit should be able to renew a transaction"); if let Some(options) = options { txn.set_options(options); } @@ -329,15 +334,15 @@ impl generic_btree::rle::Mergeable for EventHint { impl Transaction { #[inline] - pub fn new(doc: Arc) -> Self { + pub fn new(doc: Arc) -> LoroResult { Self::new_with_origin(doc.clone(), "".into()) } - pub fn new_with_origin(doc: Arc, origin: InternalString) -> Self { + pub fn new_with_origin(doc: Arc, origin: InternalString) -> LoroResult { let oplog_lock = doc.oplog.lock_unpoisoned(); let mut state_lock = doc.state.lock_unpoisoned(); if state_lock.is_in_txn() { - panic!("Cannot start a transaction while another one is in progress"); + return Err(LoroError::DuplicatedTransactionError); } state_lock.start_txn(origin, crate::event::EventTriggerKind::Local); @@ -347,12 +352,15 @@ impl Transaction { let next_counter = oplog_lock.next_id(peer).counter; let next_lamport = oplog_lock.dag.frontiers_to_next_lamport(&frontiers); let latest_timestamp = oplog_lock.get_greatest_timestamp(&frontiers); - oplog_lock - .check_change_greater_than_last_peer_id(peer, next_counter, &frontiers) - .unwrap(); + if let Err(err) = + oplog_lock.check_change_greater_than_last_peer_id(peer, next_counter, &frontiers) + { + state_lock.abort_txn(); + return Err(err); + } drop(state_lock); drop(oplog_lock); - Self { + Ok(Self { peer, doc: Arc::downgrade(&doc), arena, @@ -370,7 +378,7 @@ impl Transaction { msg: None, latest_timestamp, is_peer_first_appearance: false, - } + }) } pub fn set_origin(&mut self, origin: InternalString) { @@ -470,7 +478,14 @@ impl Transaction { let mut oplog = doc.oplog.lock_unpoisoned(); let mut state = doc.state.lock_unpoisoned(); - let mut change = oplog.uncommitted_change.take().unwrap(); + let Some(mut change) = oplog.uncommitted_change.take() else { + state.abort_txn(); + drop(state); + drop(oplog); + return Err(LoroError::internal( + "missing uncommitted change while committing transaction", + )); + }; modifier.modify_change(&mut change); let diff = if state.is_recording() { Some(change_to_diff( @@ -955,3 +970,34 @@ fn change_to_diff( ans } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{cursor::PosType, version::Frontiers}; + + #[test] + fn txn_creation_rolls_back_in_txn_after_peer_conflict() { + let doc = LoroDoc::new(); + doc.set_detached_editing(true); + doc.set_peer_id(7).unwrap(); + + let text = doc.get_text("text"); + let mut txn = doc.txn().unwrap(); + text.insert_with_txn(&mut txn, 0, "a", PosType::Unicode) + .unwrap(); + txn.commit().unwrap(); + + doc.checkout(&Frontiers::default()).unwrap(); + doc.set_peer_id(7).unwrap(); + + let err = doc + .txn() + .expect_err("stale detached frontiers should reject reusing the same peer"); + assert!(matches!( + err, + LoroError::ConcurrentOpsWithSamePeerID { peer: 7, .. } + )); + assert!(!doc.app_state().lock_unpoisoned().is_in_txn()); + } +} From 4676c31c76814c2897986435309989958530b914 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Thu, 2 Apr 2026 17:15:07 +0800 Subject: [PATCH 7/8] Return errors for snapshot encoding edge cases --- .../src/encoding/fast_snapshot.rs | 43 ++++- .../src/encoding/shallow_snapshot.rs | 177 ++++++++++-------- 2 files changed, 134 insertions(+), 86 deletions(-) diff --git a/crates/loro-internal/src/encoding/fast_snapshot.rs b/crates/loro-internal/src/encoding/fast_snapshot.rs index 009100a6b..0e2b72607 100644 --- a/crates/loro-internal/src/encoding/fast_snapshot.rs +++ b/crates/loro-internal/src/encoding/fast_snapshot.rs @@ -293,8 +293,17 @@ pub(crate) fn encode_snapshot_inner(doc: &LoroDoc) -> Snapshot { } 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]; + let oplog_len = bytes + .get(0..4) + .ok_or_else(|| LoroError::DecodeError("decode_oplog: missing length prefix".into()))?; + let oplog_len = u32::from_le_bytes( + oplog_len + .try_into() + .expect("slice length checked to be exactly 4"), + ) as usize; + let oplog_bytes = bytes.get(4..4 + oplog_len).ok_or_else(|| { + LoroError::DecodeError("decode_oplog: invalid oplog length".into()) + })?; let mut changes = ChangeStore::decode_snapshot_for_updates( oplog_bytes.to_vec().into(), &oplog.arena, @@ -324,12 +333,22 @@ pub(crate) fn decode_updates(oplog: &mut OpLog, body: Bytes) -> Result body.len() { + return Err(LoroError::DecodeError( + "decode_updates: truncated block payload".into(), + )); + } + let block_bytes = body.slice(index..end); let new_changes = ChangeStore::decode_block_bytes(block_bytes, &oplog.arena, self_vv)?; changes.extend(new_changes); - index += len; + index = end; reader = &reader[len..]; } @@ -337,6 +356,20 @@ pub(crate) fn decode_updates(oplog: &mut OpLog, body: Bytes) -> Result LoroResult { diff --git a/crates/loro-internal/src/encoding/shallow_snapshot.rs b/crates/loro-internal/src/encoding/shallow_snapshot.rs index f6fbb5eaf..c8db35354 100644 --- a/crates/loro-internal/src/encoding/shallow_snapshot.rs +++ b/crates/loro-internal/src/encoding/shallow_snapshot.rs @@ -9,7 +9,7 @@ use crate::{ dag::DagUtils, encoding::fast_snapshot::{Snapshot, _encode_snapshot}, state::container_store::FRONTIERS_KEY, - version::Frontiers, + version::{Frontiers, VersionVector}, LoroDoc, }; @@ -35,7 +35,7 @@ pub(crate) fn export_shallow_snapshot_inner( ) -> Result<(Snapshot, Frontiers), LoroEncodeError> { let oplog = doc.oplog().lock_unpoisoned(); let start_from = calc_shallow_doc_start(&oplog, start_from); - let mut start_vv = oplog.dag().frontiers_to_vv(&start_from).unwrap(); + let mut start_vv = frontiers_to_vv_for_export(&oplog, &start_from, "export_shallow_snapshot")?; for id in start_from.iter() { // we need to include the ops in start_from, this can make things easier start_vv.insert(id.peer, id.counter); @@ -74,65 +74,59 @@ pub(crate) fn export_shallow_snapshot_inner( let latest_vv = oplog.vv(); let ops_num: usize = latest_vv.sub_iter(&start_vv).map(|x| x.atom_len()).sum(); drop(oplog); - doc._checkout_without_emitting(&start_from, false, false) - .unwrap(); - let mut state = doc.app_state().lock_unpoisoned(); - let alive_containers = state.ensure_all_alive_containers(); - if has_unknown_container(alive_containers.iter()) { - return Err(LoroEncodeError::UnknownContainer); - } - let mut alive_c_bytes: BTreeSet> = - alive_containers.iter().map(|x| x.to_bytes()).collect(); - state.store.flush(); - let shallow_root_state_kv = state.store.get_kv_clone(); - drop(state); - doc._checkout_without_emitting(&latest_frontiers, false, false) - .unwrap(); - let state_bytes = if ops_num > MAX_OPS_NUM_TO_ENCODE_WITHOUT_LATEST_STATE { + let result = (|| -> Result { + doc._checkout_without_emitting(&start_from, false, false) + .map_err(LoroEncodeError::from)?; let mut state = doc.app_state().lock_unpoisoned(); - state.ensure_all_alive_containers(); - state.store.encode(); - // All the containers that are created after start_from need to be encoded - for cid in state.store.iter_all_container_ids() { - if let ContainerID::Normal { peer, counter, .. } = cid { - let temp_id = ID::new(peer, counter); - if !start_from.contains(&temp_id) { + let alive_containers = state.ensure_all_alive_containers(); + if has_unknown_container(alive_containers.iter()) { + return Err(LoroEncodeError::UnknownContainer); + } + let mut alive_c_bytes: BTreeSet> = + alive_containers.iter().map(|x| x.to_bytes()).collect(); + state.store.flush(); + let shallow_root_state_kv = state.store.get_kv_clone(); + drop(state); + doc._checkout_without_emitting(&latest_frontiers, false, false) + .map_err(LoroEncodeError::from)?; + let state_bytes = if ops_num > MAX_OPS_NUM_TO_ENCODE_WITHOUT_LATEST_STATE { + let mut state = doc.app_state().lock_unpoisoned(); + state.ensure_all_alive_containers(); + state.store.encode(); + // All the containers that are created after start_from need to be encoded + for cid in state.store.iter_all_container_ids() { + if let ContainerID::Normal { peer, counter, .. } = cid { + let temp_id = ID::new(peer, counter); + if !start_from.contains(&temp_id) { + alive_c_bytes.insert(cid.to_bytes()); + } + } else { alive_c_bytes.insert(cid.to_bytes()); } - } else { - alive_c_bytes.insert(cid.to_bytes()); } - } - - let new_kv = state.store.get_kv_clone(); - new_kv.remove_same(&shallow_root_state_kv); - new_kv.retain_keys(&alive_c_bytes); - Some(new_kv.export()) - } else { - None - }; - - shallow_root_state_kv.retain_keys(&alive_c_bytes); - shallow_root_state_kv.insert(FRONTIERS_KEY, start_from.encode().into()); - let shallow_root_state_bytes = shallow_root_state_kv.export(); - let snapshot = Snapshot { - oplog_bytes, - state_bytes, - shallow_root_state_bytes, - }; + let new_kv = state.store.get_kv_clone(); + new_kv.remove_same(&shallow_root_state_kv); + new_kv.retain_keys(&alive_c_bytes); + Some(new_kv.export()) + } else { + None + }; - if state_frontiers != latest_frontiers { - doc._checkout_without_emitting(&state_frontiers, false, false) - .unwrap(); - } + shallow_root_state_kv.retain_keys(&alive_c_bytes); + shallow_root_state_kv.insert(FRONTIERS_KEY, start_from.encode().into()); + let shallow_root_state_bytes = shallow_root_state_kv.export(); - if is_attached { - doc.set_detached(false); - } + Ok(Snapshot { + oplog_bytes, + state_bytes, + shallow_root_state_bytes, + }) + })(); + restore_export_doc_state(doc, &state_frontiers, is_attached)?; doc.drop_pending_events(); - Ok((snapshot, start_from)) + Ok((result?, start_from)) } fn has_unknown_container<'a>(mut cids: impl Iterator) -> bool { @@ -146,7 +140,7 @@ pub(crate) fn export_state_only_snapshot( ) -> Result { let oplog = doc.oplog().lock_unpoisoned(); let start_from = calc_shallow_doc_start(&oplog, start_from); - let mut start_vv = oplog.dag().frontiers_to_vv(&start_from).unwrap(); + let mut start_vv = frontiers_to_vv_for_export(&oplog, &start_from, "export_state_only_snapshot")?; for id in start_from.iter() { // we need to include the ops in start_from, this can make things easier start_vv.insert(id.peer, id.counter); @@ -168,36 +162,30 @@ pub(crate) fn export_state_only_snapshot( let state_frontiers = doc.state_frontiers(); let is_attached = !doc.is_detached(); drop(oplog); - doc._checkout_without_emitting(&start_from, false, false) - .unwrap(); - let mut state = doc.app_state().lock_unpoisoned(); - let alive_containers = state.ensure_all_alive_containers(); - let alive_c_bytes = cids_to_bytes(alive_containers); - state.store.flush(); - let shallow_state_kv = state.store.get_kv_clone(); - drop(state); - shallow_state_kv.retain_keys(&alive_c_bytes); - shallow_state_kv.insert(FRONTIERS_KEY, start_from.encode().into()); - let shallow_state_bytes = shallow_state_kv.export(); - // println!("shallow_state_bytes.len = {:?}", shallow_state_bytes.len()); - // println!("oplog_bytes.len = {:?}", oplog_bytes.len()); - let snapshot = Snapshot { - oplog_bytes, - state_bytes: None, - shallow_root_state_bytes: shallow_state_bytes, - }; - _encode_snapshot(snapshot, w); - - if state_frontiers != start_from { - doc._checkout_without_emitting(&state_frontiers, false, false) - .unwrap(); - } - - if is_attached { - doc.set_detached(false); - } + let result = (|| -> Result<(), LoroEncodeError> { + doc._checkout_without_emitting(&start_from, false, false) + .map_err(LoroEncodeError::from)?; + let mut state = doc.app_state().lock_unpoisoned(); + let alive_containers = state.ensure_all_alive_containers(); + let alive_c_bytes = cids_to_bytes(alive_containers); + state.store.flush(); + let shallow_state_kv = state.store.get_kv_clone(); + drop(state); + shallow_state_kv.retain_keys(&alive_c_bytes); + shallow_state_kv.insert(FRONTIERS_KEY, start_from.encode().into()); + let shallow_state_bytes = shallow_state_kv.export(); + let snapshot = Snapshot { + oplog_bytes, + state_bytes: None, + shallow_root_state_bytes: shallow_state_bytes, + }; + _encode_snapshot(snapshot, w); + Ok(()) + })(); + restore_export_doc_state(doc, &state_frontiers, is_attached)?; doc.drop_pending_events(); + result?; Ok(start_from) } @@ -208,6 +196,33 @@ fn cids_to_bytes( alive_c_bytes } +fn frontiers_to_vv_for_export( + oplog: &crate::OpLog, + frontiers: &Frontiers, + context: &str, +) -> Result { + oplog.dag().frontiers_to_vv(frontiers).ok_or_else(|| { + LoroEncodeError::FrontiersNotFound(format!("{context}: unreachable frontiers {frontiers:?}")) + }) +} + +fn restore_export_doc_state( + doc: &LoroDoc, + state_frontiers: &Frontiers, + was_attached: bool, +) -> Result<(), LoroEncodeError> { + if &doc.state_frontiers() != state_frontiers { + doc._checkout_without_emitting(state_frontiers, false, false) + .map_err(LoroEncodeError::from)?; + } + + if was_attached { + doc.set_detached(false); + } + + Ok(()) +} + /// Calculates optimal starting version for the shallow doc /// /// It should be the LCA of the user given version and the latest version. From a4d2d94a2bdadecfa62ad1d0bf12f29c1f47acd9 Mon Sep 17 00:00:00 2001 From: Zixuan Chen Date: Mon, 13 Apr 2026 22:22:04 +0800 Subject: [PATCH 8/8] Fix loom lock helper usage --- crates/loro-internal/src/awareness.rs | 4 ++-- crates/loro-internal/src/configure.rs | 5 ++--- crates/loro-internal/src/pre_commit.rs | 4 ++-- crates/loro-internal/src/state.rs | 3 +-- crates/loro-internal/src/state/richtext_state.rs | 2 +- crates/loro-internal/src/sync.rs | 9 +++------ 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/crates/loro-internal/src/awareness.rs b/crates/loro-internal/src/awareness.rs index 7c25907c8..d587daf8b 100644 --- a/crates/loro-internal/src/awareness.rs +++ b/crates/loro-internal/src/awareness.rs @@ -9,9 +9,9 @@ //! //! The legacy `Awareness` type remains for backward compatibility but is deprecated in //! favor of `EphemeralStore`. -use crate::sync::MutexExt as _; +use crate::sync::{Mutex, MutexExt as _}; use std::sync::atomic::AtomicI64; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use loro_common::{LoroValue, PeerID}; use rustc_hash::FxHashMap; diff --git a/crates/loro-internal/src/configure.rs b/crates/loro-internal/src/configure.rs index 31dd67638..84008318f 100644 --- a/crates/loro-internal/src/configure.rs +++ b/crates/loro-internal/src/configure.rs @@ -1,12 +1,11 @@ -use crate::sync::{MutexExt as _, RwLockExt as _}; +use crate::sync::{Mutex, MutexExt as _, RwLock, RwLockExt as _}; use loro_common::ContainerID; use rustc_hash::FxHashSet; pub use crate::container::richtext::config::{StyleConfig, StyleConfigMap}; use crate::LoroDoc; use std::sync::atomic::{AtomicBool, AtomicI64}; -use std::sync::RwLock; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; #[derive(Clone, Debug)] pub struct Configure { diff --git a/crates/loro-internal/src/pre_commit.rs b/crates/loro-internal/src/pre_commit.rs index 60ea9729a..37b446836 100644 --- a/crates/loro-internal/src/pre_commit.rs +++ b/crates/loro-internal/src/pre_commit.rs @@ -1,6 +1,6 @@ -use std::sync::{Arc, Mutex}; +use std::sync::Arc; -use crate::sync::MutexExt as _; +use crate::sync::{Mutex, MutexExt as _}; use crate::{ change::{Change, Timestamp}, oplog::get_timestamp_now_txn, diff --git a/crates/loro-internal/src/state.rs b/crates/loro-internal/src/state.rs index 9616effbb..f72fd9aa3 100644 --- a/crates/loro-internal/src/state.rs +++ b/crates/loro-internal/src/state.rs @@ -1,6 +1,5 @@ use crate::sync::MutexExt as _; -use crate::sync::{AtomicU64, Mutex}; -use std::sync::RwLock; +use crate::sync::{AtomicU64, Mutex, RwLock}; use std::sync::{Arc, Weak}; use std::{borrow::Cow, io::Write, sync::atomic::Ordering}; diff --git a/crates/loro-internal/src/state/richtext_state.rs b/crates/loro-internal/src/state/richtext_state.rs index 8b8bce4b7..599cd5033 100644 --- a/crates/loro-internal/src/state/richtext_state.rs +++ b/crates/loro-internal/src/state/richtext_state.rs @@ -3,7 +3,6 @@ use loro_common::{ContainerID, InternalString, LoroError, LoroResult, LoroValue, use loro_delta::DeltaRopeBuilder; use rustc_hash::{FxHashMap, FxHashSet}; use std::ops::Range; -use std::sync::RwLock; use std::sync::{Arc, Weak}; use crate::{ @@ -22,6 +21,7 @@ use crate::{ event::{Diff, Index, InternalDiff, TextDiff}, handler::TextDelta, op::{Op, RawOp}, + sync::RwLock, utils::{lazy::LazyLoad, string_slice::StringSlice}, LoroDocInner, }; diff --git a/crates/loro-internal/src/sync.rs b/crates/loro-internal/src/sync.rs index 06c94397d..5f376c49a 100644 --- a/crates/loro-internal/src/sync.rs +++ b/crates/loro-internal/src/sync.rs @@ -4,13 +4,9 @@ pub use loom::thread; pub use std::thread; #[cfg(loom)] -pub use loom::sync::{ - LockResult, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, -}; +pub use loom::sync::{LockResult, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; #[cfg(not(loom))] -pub use std::sync::{ - LockResult, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard, -}; +pub use std::sync::{LockResult, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard}; #[cfg(loom)] pub use loom::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, AtomicU8, AtomicUsize}; @@ -69,6 +65,7 @@ mod my_thread_local { use super::thread; use super::Mutex; + use super::MutexExt as _; use rustc_hash::FxHashMap; #[derive(Debug)]