Nimblesite / Nimblesite/SharpLsp

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

Open
#237 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
132
Forks
5
Avg merge
6h 24m
Merged PRs (30d)
27

Description

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.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the dispatch loop in src/sharplsp/src/main.rs:615, then read src/sharplsp/src/code_lens.rs:28 and the sidecar constraints in src/sharplsp/src/sidecar/manager.rs, including the correlation TODO. Trace handlers that use &mut NavCache and converge_provisional_publication at main.rs:927 before running the relevant LSP or test-explorer checks. Done means a slow sidecar request no longer prevents unrelated requests or notifications from being served, without breaking response delivery or timeout behavior.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
backend, devtools, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.