Skip to content

Commit ca05883

Browse files
fix(crdt): resolve yjs save_doc transaction deadlock
save_doc opened a read transaction (doc.transact()) and then called doc.get_or_insert_text("t"), which opens a mutable transaction internally. yrs transactions are mutually exclusive, so the mutable transact blocked on the still-held read transaction and never returned: the Part 5 awareness test (awareness_traffic_does_not_persist_to_yjs_node) hung at 0% CPU. Resolve the text root type before opening the read transaction. Verified green: rustyred-core 105 + rustyred-server 175 = 280 passed, 0 failed.
1 parent 78bb318 commit ca05883

1 file changed

Lines changed: 6 additions & 1 deletion

File tree

crates/rustyred-server/src/yjs_sync.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,14 @@ fn load_provenance(state: &AppState, tenant_id: &str, node_id: &str) -> Provenan
172172

173173
/// Persist a room's full current state as a YjsDoc node.
174174
fn save_doc(state: &AppState, tenant_id: &str, room: &YjsRoom, doc: &Doc, actor: &str) {
175+
// `get_or_insert_text` opens its own mutable transaction internally, so the
176+
// root type must be resolved BEFORE the read transaction below. Resolving it
177+
// while `txn` is held deadlocks: the mutable transact blocks on the read
178+
// transaction that never releases (yrs transactions are mutually exclusive).
179+
let text = doc.get_or_insert_text("t");
175180
let txn = doc.transact();
176181
let bytes = txn.encode_state_as_update_v1(&StateVector::default());
177-
let text_len = doc.get_or_insert_text("t").get_string(&txn).chars().count();
182+
let text_len = text.get_string(&txn).chars().count();
178183
drop(txn);
179184
let updated_at_ms = now_ms();
180185
let mut store = match state.tenant_graph_store(tenant_id) {

0 commit comments

Comments
 (0)