diff --git a/server/src/crdt_state/lww_maps/llm_sessions.rs b/server/src/crdt_state/lww_maps/llm_sessions.rs index 3600620a..52400f0a 100644 --- a/server/src/crdt_state/lww_maps/llm_sessions.rs +++ b/server/src/crdt_state/lww_maps/llm_sessions.rs @@ -61,7 +61,12 @@ pub fn read_llm_session(persona: &str) -> Option { let state_mutex = get_crdt()?; let state = state_mutex.lock().ok()?; let &idx = state.llm_session_index.get(persona)?; - extract_llm_session_view(&state.crdt.doc.llm_sessions[idx]) + // Compute the local sled id from the guard we already hold — calling + // our_node_id() here would re-lock the same non-reentrant mutex and + // self-deadlock the thread while it holds the lock, wedging every other + // CRDT user behind it (bug 1170: repeated full-sled freezes). + let local_sled_id = crate::crdt_state::hex::encode(&state.crdt.id); + extract_llm_session_view(&state.crdt.doc.llm_sessions[idx], &local_sled_id) } /// Atomically read new event-log entries for `persona` past the stored @@ -311,7 +316,14 @@ fn extract_new_event_multi( } /// Convert a CRDT LLM session entry into its read-only view representation. -pub(super) fn extract_llm_session_view(entry: &LlmSessionCrdt) -> Option { +/// +/// `local_sled_id` must be supplied by the caller: this function runs while +/// the CRDT state lock is held, so it must NOT call `our_node_id()` (which +/// acquires that same lock — see bug 1170). +pub(super) fn extract_llm_session_view( + entry: &LlmSessionCrdt, + local_sled_id: &str, +) -> Option { let session_id = match entry.session_id.view() { JsonValue::String(s) if !s.is_empty() => s, _ => return None, @@ -320,8 +332,7 @@ pub(super) fn extract_llm_session_view(entry: &LlmSessionCrdt) -> Option s, _ => String::new(), }; - let local_sled_id = crate::crdt_state::our_node_id().unwrap_or_default(); - let scope_filter = parse_scope(entry, &local_sled_id); + let scope_filter = parse_scope(entry, local_sled_id); let high_water = parse_high_water(entry); Some(LlmSessionView { session_id,