diff --git a/crates/loro-internal/src/loro.rs b/crates/loro-internal/src/loro.rs index 6a3f4936..f0eb1185 100644 --- a/crates/loro-internal/src/loro.rs +++ b/crates/loro-internal/src/loro.rs @@ -438,8 +438,7 @@ impl LoroDoc { } }; - let mut state = self.state.lock().unwrap(); - self.emit_events(&mut state); + self.emit_events(); Ok(()) } @@ -520,8 +519,12 @@ impl LoroDoc { ans } - fn emit_events(&self, state: &mut DocState) { - let events = state.take_events(); + fn emit_events(&self) { + // we should not hold the lock when emitting events + let events = { + let mut state = self.state.lock().unwrap(); + state.take_events() + }; for event in events { self.observer.emit(event); } @@ -737,7 +740,8 @@ impl LoroDoc { from_checkout: true, new_version: Cow::Owned(frontiers.clone()), }); - self.emit_events(&mut state); + drop(state); + self.emit_events(); Ok(()) } diff --git a/crates/loro-internal/tests/test.rs b/crates/loro-internal/tests/test.rs index 1c2d947f..868379e5 100644 --- a/crates/loro-internal/tests/test.rs +++ b/crates/loro-internal/tests/test.rs @@ -792,3 +792,40 @@ fn issue_batch_import_snapshot() { let doc3 = LoroDoc::new(); doc3.import_batch(&[data1, data2]).unwrap(); } + +#[test] +fn state_may_deadlock_when_import() { + // helper function ref: https://github.com/rust-lang/rfcs/issues/2798#issuecomment-552949300 + use std::time::Duration; + use std::{sync::mpsc, thread}; + fn panic_after(d: Duration, f: F) -> T + where + T: Send + 'static, + F: FnOnce() -> T, + F: Send + 'static, + { + let (done_tx, done_rx) = mpsc::channel(); + let handle = thread::spawn(move || { + let val = f(); + done_tx.send(()).expect("Unable to send completion signal"); + val + }); + + match done_rx.recv_timeout(d) { + Ok(_) => handle.join().expect("Thread panicked"), + Err(_) => panic!("Thread took too long"), + } + } + + panic_after(Duration::from_millis(100), || { + let doc = LoroDoc::new_auto_commit(); + let map = doc.get_map("map"); + doc.subscribe_root(Arc::new(move |_e| { + map.id(); + })); + + let doc2 = LoroDoc::new_auto_commit(); + doc2.get_map("map").insert("foo", 123).unwrap(); + doc.import(&doc.export_snapshot()).unwrap(); + }) +}