Skip to content

lsp: Fix retained notification subscriptions leaking dropped language servers - #58866

Merged
Anthony-Eid merged 1 commit into
mainfrom
fix-lsp-notification-leak
Jun 8, 2026
Merged

lsp: Fix retained notification subscriptions leaking dropped language servers#58866
Anthony-Eid merged 1 commit into
mainfrom
fix-lsp-notification-leak

Conversation

@Anthony-Eid

Copy link
Copy Markdown
Contributor

Subscription::Notification held a strong Arc to a language server's entire notification handler map, while Subscription::Io already used a Weak. Any subscription retained beyond the server's lifetime (e.g. copilot_log_subscription in the LSP log store, which lives until the project is closed) kept every handler on the dead server alive — along with everything those closures capture, including Arc<CachedLspAdapter> and LspAdapterDelegates that pin full worktree snapshots. Each server restart while such a subscription existed leaked one server generation; on large worktrees that's tens of MB per restart.

The fix changes Subscription::Notification to hold a Weak reference, matching the Io variant. Dropping a subscription still deregisters its handler when the server is alive; if the server is already gone, there is nothing left to clean up.

Also adds a regression test (test_subscription_leaks_handlers_after_server_drop) to prevent this from happening again

Helps with #58190 and #31461 (long-running memory growth involving language servers), though it likely isn't the whole story for either, the unbounded outbound message channel in crates/lsp/src/lsp.rs remains a separate suspect for the extreme growth in #58190.

Self-Review Checklist:

  • I've reviewed my own diff for quality, security, and reliability
  • Unsafe blocks (if any) have justifying comments
  • The content adheres to Zed's UI standards (UX/UI and icon guidelines)
  • Tests cover the new/changed behavior
  • Performance impact has been considered and is acceptable

Release Notes:

  • Fixed a memory leak where restarting a language server could leak memory if its notification subscriptions were still retained (e.g. while the LSP log view was tracking it).

@cla-bot cla-bot Bot added the cla-signed The user has signed the Contributor License Agreement label Jun 8, 2026
@zed-community-bot zed-community-bot Bot added the staff Pull requests authored by a current member of Zed staff label Jun 8, 2026
@Anthony-Eid
Anthony-Eid added this pull request to the merge queue Jun 8, 2026
Merged via the queue into main with commit 3f16f7b Jun 8, 2026
43 checks passed
@Anthony-Eid
Anthony-Eid deleted the fix-lsp-notification-leak branch June 8, 2026 21:27
This was referenced Jun 18, 2026
liusuren123 pushed a commit to liusuren123/zed that referenced this pull request Jun 24, 2026
… servers (zed-industries#58866)

`Subscription::Notification` held a strong `Arc` to a language server's
entire notification handler map, while `Subscription::Io` already used a
`Weak`. Any subscription retained beyond the server's lifetime (e.g.
`copilot_log_subscription` in the LSP log store, which lives until the
project is closed) kept **every** handler on the dead server alive —
along with everything those closures capture, including
`Arc<CachedLspAdapter>` and `LspAdapterDelegate`s that pin full worktree
snapshots. Each server restart while such a subscription existed leaked
one server generation; on large worktrees that's tens of MB per restart.

The fix changes `Subscription::Notification` to hold a `Weak` reference,
matching the `Io` variant. Dropping a subscription still deregisters its
handler when the server is alive; if the server is already gone, there
is nothing left to clean up.

Also adds a regression test
(`test_subscription_leaks_handlers_after_server_drop`) to prevent this
from happening again

Helps with zed-industries#58190 and zed-industries#31461 (long-running memory growth involving
language servers), though it likely isn't the whole story for either,
the unbounded outbound message channel in `crates/lsp/src/lsp.rs`
remains a separate suspect for the extreme growth in zed-industries#58190.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Fixed a memory leak where restarting a language server could leak
memory if its notification subscriptions were still retained (e.g. while
the LSP log view was tracking it).

@sarashed731-prog sarashed731-prog left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback

@Anthony-Eid

Copy link
Copy Markdown
Contributor Author

Feedback

What feedback did you leave? I didn't see anything

jonx pushed a commit to jonx/zed-aros that referenced this pull request Jul 17, 2026
… servers (zed-industries#58866)

`Subscription::Notification` held a strong `Arc` to a language server's
entire notification handler map, while `Subscription::Io` already used a
`Weak`. Any subscription retained beyond the server's lifetime (e.g.
`copilot_log_subscription` in the LSP log store, which lives until the
project is closed) kept **every** handler on the dead server alive —
along with everything those closures capture, including
`Arc<CachedLspAdapter>` and `LspAdapterDelegate`s that pin full worktree
snapshots. Each server restart while such a subscription existed leaked
one server generation; on large worktrees that's tens of MB per restart.

The fix changes `Subscription::Notification` to hold a `Weak` reference,
matching the `Io` variant. Dropping a subscription still deregisters its
handler when the server is alive; if the server is already gone, there
is nothing left to clean up.

Also adds a regression test
(`test_subscription_leaks_handlers_after_server_drop`) to prevent this
from happening again

Helps with zed-industries#58190 and zed-industries#31461 (long-running memory growth involving
language servers), though it likely isn't the whole story for either,
the unbounded outbound message channel in `crates/lsp/src/lsp.rs`
remains a separate suspect for the extreme growth in zed-industries#58190.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Fixed a memory leak where restarting a language server could leak
memory if its notification subscriptions were still retained (e.g. while
the LSP log view was tracking it).
jolutz pushed a commit to jolutz/zed that referenced this pull request Aug 8, 2026
… servers (zed-industries#58866)

`Subscription::Notification` held a strong `Arc` to a language server's
entire notification handler map, while `Subscription::Io` already used a
`Weak`. Any subscription retained beyond the server's lifetime (e.g.
`copilot_log_subscription` in the LSP log store, which lives until the
project is closed) kept **every** handler on the dead server alive —
along with everything those closures capture, including
`Arc<CachedLspAdapter>` and `LspAdapterDelegate`s that pin full worktree
snapshots. Each server restart while such a subscription existed leaked
one server generation; on large worktrees that's tens of MB per restart.

The fix changes `Subscription::Notification` to hold a `Weak` reference,
matching the `Io` variant. Dropping a subscription still deregisters its
handler when the server is alive; if the server is already gone, there
is nothing left to clean up.

Also adds a regression test
(`test_subscription_leaks_handlers_after_server_drop`) to prevent this
from happening again

Helps with zed-industries#58190 and zed-industries#31461 (long-running memory growth involving
language servers), though it likely isn't the whole story for either,
the unbounded outbound message channel in `crates/lsp/src/lsp.rs`
remains a separate suspect for the extreme growth in zed-industries#58190.

Self-Review Checklist:

- [x] I've reviewed my own diff for quality, security, and reliability
- [x] Unsafe blocks (if any) have justifying comments
- [x] The content adheres to Zed's UI standards
([UX/UI](https://github.com/zed-industries/zed/blob/main/CONTRIBUTING.md#uiux-checklist)
and
[icon](https://github.com/zed-industries/zed/blob/main/crates/icons/README.md)
guidelines)
- [x] Tests cover the new/changed behavior
- [x] Performance impact has been considered and is acceptable

Release Notes:

- Fixed a memory leak where restarting a language server could leak
memory if its notification subscriptions were still retained (e.g. while
the LSP log view was tracking it).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla-signed The user has signed the Contributor License Agreement staff Pull requests authored by a current member of Zed staff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants