Skip to content

Fix potential listener leak in document semantic tokens#298512

Merged
alexdima merged 2 commits intomainfrom
alexd/sheer-egret
Mar 1, 2026
Merged

Fix potential listener leak in document semantic tokens#298512
alexdima merged 2 commits intomainfrom
alexd/sheer-egret

Conversation

@alexdima
Copy link
Member

@alexdima alexdima commented Mar 1, 2026

Closes #226407

Hoist provider onDidChange and registry onDidChange subscriptions from each ModelSemanticColoring instance into the singleton DocumentSemanticTokensFeature.

Previously, every ModelSemanticColoring subscribed individually to both the global LanguageFeatureRegistry.onDidChange and each provider's onDidChange event, resulting in O(N*M) listeners (N models × M providers). In scenarios like chat editing where many models are created rapidly, these listeners accumulated and triggered leak detection.

Now the singleton subscribes once to the registry change and once per provider (via allNoModel()), then fans out notifications to watchers. Each watcher checks provider relevance via _provider.all(model).includes() before acting on the event.

Also replaces manual IDisposable[] management with a DisposableStore for proper lifecycle tracking.

Hoist provider onDidChange and registry onDidChange subscriptions from
each ModelSemanticColoring instance into the singleton
DocumentSemanticTokensFeature.

Previously, every ModelSemanticColoring subscribed individually to both
the global LanguageFeatureRegistry.onDidChange and each provider's
onDidChange event, resulting in O(N*M) listeners (N models × M
providers). In scenarios like chat editing where many models are created
rapidly, these listeners accumulated and triggered leak detection.

Now the singleton subscribes once to the registry change and once per
provider (via allNoModel()), then fans out notifications to watchers.
Each watcher checks provider relevance via _provider.all(model).includes()
before acting on the event.

Also replaces manual IDisposable[] management with a DisposableStore for
proper lifecycle tracking.
Copilot AI review requested due to automatic review settings March 1, 2026 00:13
@alexdima alexdima enabled auto-merge (squash) March 1, 2026 00:13
@alexdima alexdima self-assigned this Mar 1, 2026
@vs-code-engineering vs-code-engineering bot added this to the March 2026 milestone Mar 1, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a potential event-listener leak in document semantic tokens by centralizing provider/registry change subscriptions in the singleton DocumentSemanticTokensFeature instead of registering them per ModelSemanticColoring instance (reducing O(N×M) listeners in scenarios with many models).

Changes:

  • Hoists LanguageFeatureRegistry.onDidChange subscription from each ModelSemanticColoring into DocumentSemanticTokensFeature.
  • Adds per-provider onDidChange listeners once (via allNoModel()), then fans out notifications to active model watchers.
  • Replaces manual IDisposable[] tracking with a DisposableStore for provider change listeners.

@alexdima alexdima merged commit a94e3bb into main Mar 1, 2026
20 checks passed
@alexdima alexdima deleted the alexd/sheer-egret branch March 1, 2026 00:43
DonJayamanne pushed a commit that referenced this pull request Mar 2, 2026
* Fix potential listener leak in document semantic tokens

Hoist provider onDidChange and registry onDidChange subscriptions from
each ModelSemanticColoring instance into the singleton
DocumentSemanticTokensFeature.

Previously, every ModelSemanticColoring subscribed individually to both
the global LanguageFeatureRegistry.onDidChange and each provider's
onDidChange event, resulting in O(N*M) listeners (N models × M
providers). In scenarios like chat editing where many models are created
rapidly, these listeners accumulated and triggered leak detection.

Now the singleton subscribes once to the registry change and once per
provider (via allNoModel()), then fans out notifications to watchers.
Each watcher checks provider relevance via _provider.all(model).includes()
before acting on the event.

Also replaces manual IDisposable[] management with a DisposableStore for
proper lifecycle tracking.

* Review feedback
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[1537] potential listener LEAK detected, having 1090 listeners already. MOST frequent listener (1090):

3 participants