Skip to content

fix(lsp): re-entrant notification handlers can deadlock #758

Description

@PierrunoYT

Summary

LSP server notifications are dispatched synchronously on the client's sole read-loop goroutine. If a notification handler calls Client.Call, it waits for a response that only the blocked read loop can receive, causing a deadlock until the call context expires or the connection closes.

Affected code

  • internal/lsp/client.go:99-137n- internal/lsp/client.go:151-180n

Details

Client.readLoop invokes the configured NotificationHandler inline when it receives a server notification. The public handler contract does not prohibit re-entrant client calls. A handler can therefore send a request with Client.Call and block waiting for its response while the same goroutine responsible for reading and delivering that response remains inside the handler.

The current production diagnostics handler does not make a re-entrant call, but future handlers and other package consumers can trigger this behavior.

Suggested fix

Dispatch notifications through a bounded serialized worker so notification ordering is preserved without blocking the protocol reader. Define the callback lifecycle and shutdown behavior, and add a regression test where a notification handler performs a request and receives its response without deadlocking.

Reference

Item AUD-008 from the 2026-07-18 codebase audit (docs/CODEBASE_AUDIT_2026-07-18.md).

Metadata

Metadata

Assignees

No one assigned

    Labels

    issue-approvedReviewed and approved by the core team; community PRs may implement this issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions