@@ -35,7 +35,7 @@ use parking_lot::lock_api::ReentrantMutex;
3535use rle:: HasLength ;
3636use serde:: { Deserialize , Serialize } ;
3737use std:: {
38- cell:: RefCell ,
38+ cell:: { Cell , RefCell } ,
3939 cmp:: Ordering ,
4040 collections:: VecDeque ,
4141 ops:: ControlFlow ,
@@ -89,6 +89,10 @@ pub fn set_debug() {
8989type JsResult < T > = Result < T , JsValue > ;
9090type EventCallback = Box < dyn Fn ( & SafeJsValue ) -> bool + Send + Sync + ' static > ;
9191
92+ thread_local ! {
93+ static IN_PRE_COMMIT_CALLBACK : Cell <bool > = Cell :: new( false ) ;
94+ }
95+
9296/// The CRDTs document. Loro supports different CRDTs include [**List**](LoroList),
9397/// [**RichText**](LoroText), [**Map**](LoroMap) and [**Movable Tree**](LoroTree),
9498/// you could build all kind of applications by these.
@@ -1508,7 +1512,15 @@ impl LoroDoc {
15081512 #[ wasm_bindgen( js_name = "exportJsonInIdSpan" , skip_typescript) ]
15091513 pub fn exportJsonInIdSpan ( & self , idSpan : JsIdSpan ) -> JsResult < JsValue > {
15101514 let id_span = js_to_id_span ( idSpan) ?;
1511- let json = self . doc . export_json_in_id_span ( id_span) ;
1515+ // Most LoroDoc reads run in an implicit-commit barrier (export/checkout/etc.).
1516+ // `exportJsonInIdSpan` is special: it is often called from `subscribePreCommit`,
1517+ // where the txn lock is already held. In that case, triggering another implicit
1518+ // commit would deadlock/panic. We skip the barrier while inside pre-commit.
1519+ let json = if IN_PRE_COMMIT_CALLBACK . with ( |f| f. get ( ) ) {
1520+ self . doc . export_json_in_id_span ( id_span)
1521+ } else {
1522+ self . doc . with_barrier ( || self . doc . export_json_in_id_span ( id_span) )
1523+ } ;
15121524 let s = serde_wasm_bindgen:: Serializer :: new ( ) . serialize_maps_as_objects ( true ) ;
15131525 let v = json
15141526 . serialize ( & s)
@@ -2235,7 +2247,13 @@ impl LoroDoc {
22352247 & ChangeModifier ( e. modifier . clone ( ) ) . into ( ) ,
22362248 )
22372249 . unwrap ( ) ;
2238- if let Err ( e) = observer. call1 ( & obj. into ( ) ) {
2250+ let res = IN_PRE_COMMIT_CALLBACK . with ( |f| {
2251+ let prev = f. replace ( true ) ;
2252+ let res = observer. call1 ( & obj. into ( ) ) ;
2253+ f. set ( prev) ;
2254+ res
2255+ } ) ;
2256+ if let Err ( e) = res {
22392257 console_error ! ( "Error: {:?}" , e) ;
22402258 }
22412259 true
@@ -6626,7 +6644,9 @@ interface LoroDoc<T extends Record<string, Container> = Record<string, Container
66266644 *
66276645 * This method can also export pending changes from the uncommitted transaction that have not yet been applied to the OpLog.
66286646 *
6629- * This method will NOT trigger a new commit implicitly.
6647+ * This method will implicitly commit pending local operations (like `export(...)`) so callers can
6648+ * observe the latest local edits. When called inside `subscribePreCommit(...)`, it will NOT trigger
6649+ * an additional implicit commit.
66306650 *
66316651 * @param idSpan - The id span to export.
66326652 * @returns The changes in the given id span.
0 commit comments