rust-lang/rust-analyzer · error
{join_e}
Error message
{join_e} What it means
Same `run_session` error handling, but for the case where the main loop finished normally while `io_threads.join()` still returned an error. The IO threads failed (read/write over the transport errored) even though the loop itself saw no fatal event.
Source
Thrown at crates/rust-analyzer/src/session.rs:166
let not = lsp_server::Notification::new(
ShowMessageNotification::METHOD.into(),
ShowMessageParams { kind: MessageType::Warning, message: notice },
);
connection.sender.send(lsp_server::Message::Notification(not)).unwrap();
}
if config.discover_workspace_config().is_none()
&& !config.has_linked_projects()
&& config.detached_files().is_empty()
{
config.rediscover_workspaces();
}
// If the io_threads have an error, there's usually an error on the main
// loop too because the channels are closed. Ensure we report both errors.
match (crate::main_loop(config, connection), io_threads.join()) {
(Err(loop_e), Err(join_e)) => anyhow::bail!("{loop_e}\n{join_e}"),
(Ok(_), Err(join_e)) => anyhow::bail!("{join_e}"),
(Err(loop_e), Ok(_)) => anyhow::bail!("{loop_e}"),
(Ok(_), Ok(_)) => {}
}
tracing::info!("server did shut down");
Ok(())
}
fn patch_path_prefix(path: PathBuf) -> PathBuf {
use std::path::{Component, Prefix};
if cfg!(windows) {
// VSCode might report paths with the file drive in lowercase, but this can mess
// with env vars set by tools and build scripts executed by r-a such that it invalidates
// cargo's compilations unnecessarily. https://github.com/rust-lang/rust-analyzer/issues/14683
// So we just uppercase the drive letter here unconditionally.
// (doing it conditionally is a pain because std::path::Prefix always reports uppercase letters on windows)
let mut comps = path.components();
match comps.next() {View on GitHub (pinned to e8f7e90aa3)
Solutions
- Inspect the joined error text for the underlying OS error (broken pipe, connection reset)
- Ensure the client reads all responses and does not close stdout early
- Perform a full shutdown/exit handshake before closing the transport
- Retry in the client if it was a transient connection issue over a socket-based transport
Defensive patterns
Strategy: try-catch
Try / catch
if let Err(e) = run_session(config, connection).await {
// io_threads error with clean main loop: inspect transport cause
eprintln!("io failure: {e:#}");
} Prevention
- Do not close the client's read side before consuming all responses
- Handle EPIPE/ECONNRESET in wrappers that own the transport
- Graceful shutdown before tearing down pipes or sockets
When it happens
Trigger: Calling `run_session` where `main_loop` returns `Ok` but `io_threads.join()` returns `Err`, typically when a write to the client fails (client stopped reading / closed stdout) during or at the end of a session.
Common situations: Client closes its stdout-read side early; EPIPE/ECONNRESET writing responses; container/SSH session tearing down the pipe.
Related errors
- {loop_e} {join_e}
- No file available to rename
- inconsistent text range
- No references found at position
- client exited without proper shutdown sequence
AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03).
Data as JSON: /api/errors/231f960e3bbb1e42.
Report an issue: GitHub.