Skip to content

LSP host serializes all requests: one slow sidecar call blocks the entire server for up to 2 minutes #237

Description

@MelbourneDeveloper

Summary

The Rust host serves LSP requests one at a time on a single dispatch loop, and every
sidecar-backed handler blocks that loop with runtime.block_on. One slow request therefore
stalls every later request — including requests for the other language, and syntax-only
requests that are supposed to answer in <5ms.

src/sharplsp/src/main.rs:615

for msg in &connection.receiver {
    Message::Request(req) => { handle_request(...)?; }   // inline, synchronous

src/sharplsp/src/code_lens.rs:28

let response_bytes = match runtime.block_on(sidecar.request("textDocument/codeLens", payload))

The budget on that call is REQUEST_TIMEOUT = 2 minutes
(src/sharplsp/src/sidecar/manager.rs:29), so the worst-case stall is two minutes of a
completely unresponsive server. SidecarManager::request's own doc-comment already names the
hazard ("a wedged sidecar handler blocks the LSP main loop forever") but treats the per-method
budget as the mitigation — the budget bounds the stall, it does not remove it.

Observed impact

CI run 33612748733, attempt 1, job VS Code (Windows) / testexplorer
(job id 100196280123). Three consecutive tests failed, each burning its entire 5000ms
ceiling to the millisecond:

09:36:51.300  PASS  a C# test file exposes Run + Debug test lenses        (179ms)
09:36:56.530  FAIL  an F# test file exposes Run + Debug lenses            Timeout of 5000ms
09:37:01.568  FAIL  disabling sharplsp.testLens.enabled ...               Timeout of 5000ms
09:37:06.618  FAIL  a non-test C# file produces no test lenses ...        Timeout of 5000ms
09:37:06.647  PASS  formatDuration renders the lens status suffix         (pure, instant)

Tests 2 and 3 open C# files. They had nothing to do with F#. They timed out because the
first F# textDocument/codeLens — a cold FCS project crack — was still holding the dispatch
loop, so their own requests were never served. The last test passes instantly because it never
touches the LSP.

An exactly-5000ms cascade across three tests is the signature of a wedge, not of slowness: a
merely-slow server produces varying durations.

Why it matters outside CI

textDocument/codeLens is issued by the editor unprompted, for every visible document, on
open and on every change. It is the request most likely to be sitting in front of user-initiated
work. While one is in flight against a cold or slow sidecar, the user gets no completions, no
hover, no go-to-definition — in either language — and queued didChange notifications are not
applied, so the VFS goes stale behind the block.

Why the obvious fixes do not work

  • Shorten the budget for cheap methods. fail_timed_out_request kills the sidecar on
    timeout (manager.rs:258). A 3s codeLens budget would kill FCS every time a cold crack took
    3.1s.
  • Abandon the future. Not possible: the transport is a single mutex-guarded
    write-then-read pair with no correlation id, so dropping a read mid-stream leaves a stale
    frame for the next caller — documented at manager.rs:377 and
    TODO [SIDECAR-IPC-CORRELATION] at manager.rs:47.

The fix has to be not blocking the loop: dispatch sidecar-backed handlers onto the tokio
runtime and send their responses from there (connection.sender is a cloneable crossbeam
Sender, and LSP permits out-of-order responses). Note the handlers that take &mut NavCache
need that state made shareable first, and converge_provisional_publication
(main.rs:927) must move to its async form inside the spawned task rather than
runtime.block_on.

Not covered here

The test-side trigger — a cold F# semantic load charged to a 5000ms test body — is fixed
separately by warming the code-lens path in suiteSetup. That removes the trigger for this
particular flake; this issue is the amplifier that turned one slow request into three failures,
and it remains.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions