rust-lang/rust-analyzer · error

{loop_e} {join_e}

Error message

{loop_e}
{join_e}

What it means

In `run_session`, when both the main loop (`crate::main_loop`) and the IO threads (`io_threads.join()`) fail, both errors are combined into one report via `anyhow::bail!("{loop_e}\n{join_e}")`. This exists so that an IO failure (e.g. broken pipe) that also kills the main loop does not hide either cause.

Source

Thrown at crates/rust-analyzer/src/session.rs:165

        };
        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();

View on GitHub (pinned to e8f7e90aa3)

Solutions

  1. Read both stacked messages: the second line (IO error) usually reveals the root transport failure (e.g. broken pipe)
  2. Fix the client to perform a proper shutdown/exit sequence (see the main-loop error)
  3. Check that the client keeps stdin/stdout open for the lifetime of the session
  4. If run under a supervisor, ensure the process is not being killed or disconnected externally
Defensive patterns

Strategy: try-catch

Try / catch

match run_session(config, connection).await {
    Err(e) => eprintln!("session failed:\n{e:#}"), // both loop and io errors are in the chain
    Ok(()) => {}
}

Prevention

When it happens

Trigger: Calling `rust_analyzer::run_session` where `main_loop` returns `Err` (e.g. error 80) and simultaneously `io_threads.join()` returns `Err` because the client closed/errored the transport.

Common situations: Client terminates abruptly over stdio: read/write threads fail and the main loop's channel closes; transport (socket/pipe) dies mid-session.

Related errors


AI-assisted analysis of rust-lang/rust-analyzer@e8f7e90aa3 (2026-09-03). Data as JSON: /api/errors/f36e98018c5ee696. Report an issue: GitHub.