diff --git a/crates/loro-core/src/configure.rs b/crates/loro-core/src/configure.rs index 27a85d1b..d8158182 100644 --- a/crates/loro-core/src/configure.rs +++ b/crates/loro-core/src/configure.rs @@ -20,7 +20,7 @@ impl Debug for Configure { } } -pub trait SecureRandomGenerator { +pub trait SecureRandomGenerator: Send + Sync { fn fill_byte(&self, dest: &mut [u8]); fn next_u64(&self) -> u64 { let mut buf = [0u8; 8]; diff --git a/crates/loro-core/src/container.rs b/crates/loro-core/src/container.rs index d961694f..8ddc2926 100644 --- a/crates/loro-core/src/container.rs +++ b/crates/loro-core/src/container.rs @@ -68,7 +68,7 @@ impl TryFrom<&str> for ContainerType { } } -pub trait Container: Debug + Any + Unpin { +pub trait Container: Debug + Any + Unpin + Send + Sync { fn id(&self) -> &ContainerID; fn type_(&self) -> ContainerType; fn get_value(&self) -> LoroValue; diff --git a/crates/loro-core/src/container/list/list_op.rs b/crates/loro-core/src/container/list/list_op.rs index 4eb37824..a2fa9802 100644 --- a/crates/loro-core/src/container/list/list_op.rs +++ b/crates/loro-core/src/container/list/list_op.rs @@ -311,7 +311,7 @@ impl Sliceable for InnerListOp { } } -#[cfg(test)] +#[cfg(all(test, feature = "test_utils"))] mod test { use rle::{Mergable, Sliceable}; diff --git a/crates/loro-core/src/container/text/tracker.rs b/crates/loro-core/src/container/text/tracker.rs index 54b122a6..12dc3fd3 100644 --- a/crates/loro-core/src/container/text/tracker.rs +++ b/crates/loro-core/src/container/text/tracker.rs @@ -57,6 +57,11 @@ pub struct Tracker { id_to_cursor: CursorMap, } +// SAFETY: Tracker is safe to be sent to another thread +unsafe impl Send for Tracker {} +// SAFETY: &Tracker is safe to be shared by threads +unsafe impl Sync for Tracker {} + impl From for u128 { fn from(id: ID) -> Self { ((id.client_id as u128) << 64) | id.counter as u128 diff --git a/crates/loro-core/src/container/text/tracker/cursor_map.rs b/crates/loro-core/src/container/text/tracker/cursor_map.rs index 140d412c..9f9ad046 100644 --- a/crates/loro-core/src/container/text/tracker/cursor_map.rs +++ b/crates/loro-core/src/container/text/tracker/cursor_map.rs @@ -22,7 +22,7 @@ use super::y_span::{YSpan, YSpanTreeTrait}; // marker can only live while the bumpalo is alive. So we are safe to use 'static here #[non_exhaustive] #[derive(Debug, Clone, EnumAsInner, PartialEq, Eq)] -pub enum Marker { +pub(super) enum Marker { Insert { ptr: NonNull>, len: usize, @@ -195,7 +195,7 @@ impl Mergable for Marker { } #[derive(Debug, Default)] -pub struct CursorMap(RangeMap); +pub(super) struct CursorMap(RangeMap); impl Deref for CursorMap { type Target = RangeMap; diff --git a/crates/loro-core/src/event.rs b/crates/loro-core/src/event.rs index 1485d7f8..a22d0946 100644 --- a/crates/loro-core/src/event.rs +++ b/crates/loro-core/src/event.rs @@ -73,6 +73,6 @@ pub struct MapDiff { pub deleted: FxHashSet, } -pub type Observer = Box; +pub type Observer = Box; pub type SubscriptionID = u32; diff --git a/crates/loro-core/src/fuzz/recursive.rs b/crates/loro-core/src/fuzz/recursive.rs index 4392786e..48b15f06 100644 --- a/crates/loro-core/src/fuzz/recursive.rs +++ b/crates/loro-core/src/fuzz/recursive.rs @@ -1,4 +1,9 @@ -use std::{cell::RefCell, collections::HashSet, fmt::Debug, rc::Rc}; +use std::{ + collections::HashSet, + fmt::Debug, + rc::Rc, + sync::{Arc, Mutex}, +}; use arbitrary::Arbitrary; use enum_as_inner::EnumAsInner; @@ -45,10 +50,10 @@ pub enum Action { struct Actor { loro: LoroCore, - value_tracker: Rc>, - map_tracker: Rc>>, - list_tracker: Rc>>, - text_tracker: Rc>, + value_tracker: Arc>, + map_tracker: Arc>>, + list_tracker: Arc>>, + text_tracker: Arc>, map_containers: Vec, list_containers: Vec, text_containers: Vec, @@ -58,7 +63,7 @@ impl Actor { fn new(id: ClientID) -> Self { let mut actor = Actor { loro: LoroCore::new(Default::default(), Some(id)), - value_tracker: Rc::new(RefCell::new(LoroValue::Map(Default::default()))), + value_tracker: Arc::new(Mutex::new(LoroValue::Map(Default::default()))), map_tracker: Default::default(), list_tracker: Default::default(), text_tracker: Default::default(), @@ -67,19 +72,19 @@ impl Actor { text_containers: Default::default(), }; - let root_value = Rc::clone(&actor.value_tracker); + let root_value = Arc::clone(&actor.value_tracker); actor.loro.subscribe_deep(Box::new(move |event| { - let mut root_value = root_value.borrow_mut(); + let mut root_value = root_value.lock().unwrap(); root_value.apply(&event.relative_path, &event.diff); })); let log_store = actor.loro.log_store.write().unwrap(); let mut hierarchy = actor.loro.hierarchy.try_lock().unwrap(); - let text = Rc::clone(&actor.text_tracker); + let text = Arc::clone(&actor.text_tracker); hierarchy.subscribe( &ContainerID::new_root("text", ContainerType::Text), Box::new(move |event| { - let mut text = text.borrow_mut(); + let mut text = text.lock().unwrap(); for diff in event.diff.iter() { match diff { Diff::Text(delta) => { @@ -106,11 +111,11 @@ impl Actor { false, ); - let map = Rc::clone(&actor.map_tracker); + let map = Arc::clone(&actor.map_tracker); hierarchy.subscribe( &ContainerID::new_root("map", ContainerType::Map), Box::new(move |event| { - let mut map = map.borrow_mut(); + let mut map = map.lock().unwrap(); for diff in event.diff.iter() { match diff { Diff::Map(map_diff) => { @@ -131,11 +136,11 @@ impl Actor { false, ); - let list = Rc::clone(&actor.list_tracker); + let list = Arc::clone(&actor.list_tracker); hierarchy.subscribe( &ContainerID::new_root("list", ContainerType::List), Box::new(move |event| { - let mut list = list.borrow_mut(); + let mut list = list.lock().unwrap(); for diff in event.diff.iter() { match diff { Diff::List(delta) => { @@ -590,24 +595,27 @@ fn check_eq(a_actor: &mut Actor, b_actor: &mut Actor) { let a_result = a_doc.to_json(); debug_log::debug_log!("{}", a_result.to_json_pretty()); assert_eq!(&a_result, &b_doc.to_json()); - assert_value_eq(&a_result, &a_actor.value_tracker.borrow()); + assert_value_eq(&a_result, &a_actor.value_tracker.lock().unwrap()); let a = a_doc.get_text("text"); let value_a = a.get_value(); assert_eq!( &**value_a.as_string().unwrap(), - &*a_actor.text_tracker.borrow(), + &*a_actor.text_tracker.lock().unwrap(), ); let a = a_doc.get_map("map"); let value_a = a.get_value(); - assert_eq!(&**value_a.as_map().unwrap(), &*a_actor.map_tracker.borrow()); + assert_eq!( + &**value_a.as_map().unwrap(), + &*a_actor.map_tracker.lock().unwrap() + ); let a = a_doc.get_list("list"); let value_a = a.get_value(); assert_eq!( &**value_a.as_list().unwrap(), - &*a_actor.list_tracker.borrow(), + &*a_actor.list_tracker.lock().unwrap(), ); } diff --git a/crates/loro-core/tests/test.rs b/crates/loro-core/tests/test.rs index 8691162e..8014d46d 100644 --- a/crates/loro-core/tests/test.rs +++ b/crates/loro-core/tests/test.rs @@ -1,17 +1,20 @@ -use std::cell::RefCell; -use std::rc::Rc; - use ctor::ctor; use loro_core::container::registry::ContainerWrapper; use loro_core::container::ContainerID; use loro_core::context::Context; -use loro_core::event::Index; use loro_core::id::ID; use loro_core::log_store::{EncodeConfig, EncodeMode}; use loro_core::{ContainerType, LoroCore, LoroValue, VersionVector}; +#[test] +fn send_sync() { + fn example(l: T) {} + let loro = LoroCore::default(); + example(loro); +} + #[test] fn example_list() { let mut doc = LoroCore::default(); @@ -57,11 +60,13 @@ fn subscribe_deep() { #[test] #[cfg(feature = "json")] fn text_observe() { + use std::sync::{Arc, Mutex}; + let mut doc = LoroCore::default(); - let track_value = Rc::new(RefCell::new(LoroValue::Map(Default::default()))); - let moved_value = Rc::clone(&track_value); + let track_value = Arc::new(Mutex::new(LoroValue::Map(Default::default()))); + let moved_value = Arc::clone(&track_value); doc.subscribe_deep(Box::new(move |event| { - let mut v = RefCell::borrow_mut(&*moved_value); + let mut v = moved_value.lock().unwrap(); v.apply(&event.relative_path, &event.diff); })); let mut map = doc.get_map("meta"); @@ -74,14 +79,14 @@ fn text_observe() { let todo_item = list.insert(&doc, 0, ContainerType::Map).unwrap().unwrap(); let mut todo_item = doc.get_map(todo_item); todo_item.insert(&doc, "todo", "coding").unwrap(); - assert_eq!(&doc.to_json(), &*RefCell::borrow(&track_value)); + assert_eq!(&doc.to_json(), &*track_value.lock().unwrap()); let mut text = doc.get_text("text"); text.insert(&doc, 0, "hello ").unwrap(); let mut doc_b = LoroCore::default(); let mut text_b = doc_b.get_text("text"); text_b.insert(&doc_b, 0, "world").unwrap(); doc.import(doc_b.export(Default::default())); - assert_eq!(&doc.to_json(), &*RefCell::borrow(&track_value)); + assert_eq!(&doc.to_json(), &*track_value.lock().unwrap()); println!("{}", doc.to_json().to_json()); } diff --git a/crates/loro-wasm/src/lib.rs b/crates/loro-wasm/src/lib.rs index aab7f57f..3d8ec54b 100644 --- a/crates/loro-wasm/src/lib.rs +++ b/crates/loro-wasm/src/lib.rs @@ -65,6 +65,38 @@ impl SecureRandomGenerator for MathRandom { } } +mod observer { + use std::thread::ThreadId; + + use wasm_bindgen::JsValue; + + /// We need to wrap the observer function in a struct so that we can implement Send for it. + /// But it's not Send essentially, so we need to check it manually in runtime. + pub(crate) struct Observer { + f: js_sys::Function, + thread: ThreadId, + } + + impl Observer { + pub fn new(f: js_sys::Function) -> Self { + Self { + f, + thread: std::thread::current().id(), + } + } + + pub fn call1(&self, arg: &JsValue) { + if std::thread::current().id() == self.thread { + self.f.call1(&JsValue::NULL, arg).unwrap(); + } else { + panic!("Observer called from different thread") + } + } + } + + unsafe impl Send for Observer {} +} + #[wasm_bindgen] impl Loro { #[wasm_bindgen(constructor)] @@ -192,9 +224,9 @@ impl Loro { // TODO: convert event and event sub config pub fn subscribe(&self, f: js_sys::Function) -> u32 { + let observer = observer::Observer::new(f); self.0.borrow_mut().subscribe_deep(Box::new(move |e| { - f.call1(&JsValue::NULL, &JsValue::from_bool(e.local)) - .unwrap(); + observer.call1(&JsValue::from_bool(e.local)); })) } diff --git a/crates/rle/src/range_map.rs b/crates/rle/src/range_map.rs index b82b642d..940bef62 100644 --- a/crates/rle/src/range_map.rs +++ b/crates/rle/src/range_map.rs @@ -650,6 +650,4 @@ mod test { vec![&V::new(0, 3, "a"), &V::new(3, 6, "c")] ); } - - static_assertions::assert_not_impl_any!(RangeMap>: Sync, Send); } diff --git a/crates/rle/src/rle_tree.rs b/crates/rle/src/rle_tree.rs index c5b7d382..7880671a 100644 --- a/crates/rle/src/rle_tree.rs +++ b/crates/rle/src/rle_tree.rs @@ -30,6 +30,11 @@ pub struct RleTree + 'static> { pub node: ::Boxed<'this, Node<'this, T, A>>, } +// SAFETY: tree is safe to send to another thread +unsafe impl + 'static> Send for RleTree {} +// SAFETY: &tree is safe to be shared between threads +unsafe impl + 'static> Sync for RleTree {} + impl + 'static> Default for RleTree { fn default() -> Self { assert!(