Nimblesite / Nimblesite/SharpLsp
LSP host serializes all requests: one slow sidecar call blocks the entire server for up to 2 minutes
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_requestkills 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 atmanager.rs:377and
TODO [SIDECAR-IPC-CORRELATION]atmanager.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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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