SIGN IN SIGN UP

fix(lsp): detach workspace/diagnostic/refresh from notification handlers to prevent deadlock (#25438)

## What this PR does

Fixes the permanent language-server deadlock reported in #24955.

`workspace/diagnostic/refresh` is a server-to-client *request*, and a
spec-compliant client may re-pull `textDocument/diagnostic` from this
same server before replying to it. Three notification handlers
(`initialized`, `did_change_configuration`, `did_change_watched_files`)
awaited that round-trip inside the handler itself, pinning one of the
transport's concurrency slots (tower-lsp-server defaults to 4) for as
long as the client took to reply. Four rapid watcher events — e.g. four
saves of `.oxlintrc.json` or any `tsconfig*.json` in pull-diagnostic
mode — pinned all four slots on handlers waiting for the client, while
the client waited on diagnostic pulls the server could no longer
service. Circular wait, no timeout, no recovery until the process is
killed.

The refresh is advisory — nothing consumes its result beyond logging a
warning — so this PR detaches it: a new
`Backend::spawn_diagnostic_refresh()` clones the client and spawns the
round-trip, returning the handler's slot immediately. The server keeps
answering requests with any number of refresh replies outstanding. This
is the issue's first suggested fix; raising the concurrency level
(#25153) only widens the window rather than closing it, as the issue
notes.

## How it's tested

`test_outstanding_diagnostic_refreshes_do_not_wedge_the_server` runs in
the crate's existing wire-level `TestServer` harness — real LSP framing
over duplex streams, the real `Server` at its real default concurrency
of 4. It initializes in pull mode with dynamic watchers, fires four
watched-file events, reads each resulting `workspace/diagnostic/refresh`
request **without replying** (what a busy client looks like), then sends
`shutdown` and requires a response within a 10s timeout.

Verified both directions: against the previous code the test fails by
timeout at exactly the reported wedge point; with the fix, the full
crate suite passes (85/85). `cargo fmt` and `clippy --all-targets` are
clean for the changed files. The only other change is adding tokio's
`time` feature to dev-dependencies for the test's timeout.

## AI disclosure

Investigated, implemented, and verified with AI assistance (Claude
Code), per the contributing guide's disclosure policy. I traced the root
cause to the transport's concurrency accounting, confirmed the circular
wait matches the reporter's reproduction, and validated the fix with the
deadlock-reproducing regression test above before submission.

Co-authored-by: Alexander S. <sysix@sysix-coding.de>
H
Hamody We committed
d893fd4cabf37189a23d134d5d72958675f61b69
Parent: e5c38c9
Committed by GitHub <noreply@github.com> on 8/23/2026, 11:22:58 PM