{"record":{"id":"d87a80ed87e0c4fc","repo":"Hmbown/CodeWhale","slug":"lsp-diagnostics-timed-out-waiting-for-another-document","errorCode":null,"errorMessage":"LSP diagnostics timed out waiting for another document","messagePattern":"LSP diagnostics timed out waiting for another document","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/lsp/client.rs","lineNumber":387,"sourceCode":"impl LspTransport for StdioLspTransport {\n    fn is_alive(&self) -> bool {\n        // stderr may close independently. The writer, reader and dispatcher\n        // are the protocol lifetime; none may have exited or been aborted.\n        !self.tx_outbound.is_closed() && self.tasks.iter().skip(1).all(|task| !task.is_finished())\n    }\n\n    async fn diagnostics_for(\n        &self,\n        path: &Path,\n        text: &str,\n        wait: Duration,\n    ) -> Result<DiagnosticPublication> {\n        // One receiver cannot serve concurrent polling safely: serialize the\n        // open/version/send/wait transaction, including semantic ensure_open.\n        let deadline = tokio::time::Instant::now() + wait;\n        let _gate = timeout(wait, self.diagnostics_gate.lock())\n            .await\n            .map_err(|_| anyhow!(\"LSP diagnostics timed out waiting for another document\"))?;\n        let path_buf = path.to_path_buf();\n        let (_, version) = timeout(\n            deadline.saturating_duration_since(tokio::time::Instant::now()),\n            self.open_or_change(path, text),\n        )\n        .await\n        .map_err(|_| anyhow!(\"LSP diagnostics timed out sending document\"))??;\n        loop {\n            let remaining = deadline.saturating_duration_since(tokio::time::Instant::now());\n            if remaining.is_zero() {\n                return Err(anyhow!(\n                    \"LSP diagnostics timed out before a current publication\"\n                ));\n            }\n            let mut rx = self.diagnostics_rx.lock().await;\n            let (file, published_version, items) = match timeout(remaining, rx.recv()).await {\n                Ok(Some(item)) => item,\n                Ok(None) => {","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/lsp/client.rs#L369-L405","documentation":"`diagnostics_for` serializes open/version/send/wait transactions behind a mutex because one receiver cannot serve concurrent polling. If the gate cannot be acquired within `wait`, this error fires instead of risking interleaved reads on the diagnostics channel.","triggerScenarios":"A second diagnostics request arrives while another document's wait transaction still holds `diagnostics_gate` and the first one does not release it within `wait`.","commonSituations":"UI polling diagnostics for many open files concurrently with a short wait budget; a previous diagnostics wait stuck on a hung server.","solutions":["Serialize diagnostics requests in the caller instead of issuing them concurrently","Increase the wait budget for the gate acquisition","Add per-request timeout so a stuck transaction cannot starve the gate","Use separate transports/channels per document if concurrency is required"],"exampleFix":"// before\nlet handles = files.iter().map(|f| tokio::spawn(diagnostics_for(f)));\n// after\nfor f in &files {\n    diagnostics_for(f).await?; // one transaction at a time\n}","handlingStrategy":"try-catch","validationCode":"// track in-flight diagnostics per transport before calling\nif (diagnosticsInFlight.has(transportId)) await diagnosticsInFlight.get(transportId);","typeGuard":null,"tryCatchPattern":"match diagnostics_for(path, text, wait).await {\n    Err(e) if e.to_string().contains(\"waiting for another document\") => {\n        eprintln!(\"diagnostics busy; retry after current document completes\");\n    }\n    other => other,\n}","preventionTips":["Issue diagnostics requests sequentially per transport","Use a generous wait for the gate, distinct from the server-wait budget","Ensure every gated section releases the lock on error paths"],"tags":["lsp","concurrency","mutex","timeout"],"backgroundTag":"request-timeout","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}