Memory Leak Audit

microsoft/vscode/.github/skills/memory-leak-audit

作者 microsoft001f205a1f401a09dd530b1ac1a54424a2225d0d無授權條款193K 個星標收錄於 2026年10月9日更新於 2026年10月9日儲存庫今天更新

Audit code for memory leaks and disposable issues. Use when reviewing event listeners, DOM handlers, lifecycle callbacks, or fixing leak reports. Covers addDisposableListener, Event.once, MutableDisposable, DisposableStore, and onWillDispose patterns.

AI 產生的概覽

審查程式碼中的記憶體洩漏與可處置物件問題,涵蓋事件監聽器、生命週期回呼與處置模式。

功能
提供逐步審查清單,用於找出並修正程式碼中的記憶體洩漏,重點在可處置物件模式,例如 addDisposableListener、Event.once、MutableDisposable、DisposableStore 與 onWillDispose。每個步驟會給出規則、錯誤與正確的程式碼範例,並引用已驗證的提取要求。內容也包含情境、模式與反模式的速查表,以及驗證步驟,例如檢查監聽器數量與執行洩漏檢查測試工具。
適用情境
適用於審查註冊事件監聽器或 DOM 處理器的程式碼、修正監聽器數量隨時間增加的已回報記憶體洩漏、在會被反覆呼叫的方法中建立物件,以及處理模型生命週期事件和在建構函式或初始化方法中加入事件訂閱的情境。
執行需求
不需要指令碼或工具,僅為指示型技能。驗證指引中提及受審查專案中的測試工具與 npm 效能指令。

Memory Leak Audit

The #1 bug category in VS Code. This skill encodes the patterns that prevent and fix leaks.

When to Use

  • Reviewing code that registers event listeners or DOM handlers
  • Fixing reported memory leaks (listener counts growing over time)
  • Creating objects in methods that are called repeatedly
  • Working with model lifecycle events (onWillDispose, onDidClose)
  • Adding event subscriptions in constructors or setup methods

Audit Checklist

Work through each check in order. A single missed pattern can cause thousands of leaked objects.

Step 1: DOM Event Listeners

Rule: Never use raw .onload, .onclick, or addEventListener() directly. Always use addDisposableListener().

typescript
// BAD — leaks a listener every callthis.iconElement.onload = () => { ... };
// GOOD — tracked and disposablethis._register(addDisposableListener(this.iconElement, 'load', () => { ... }));

Validated by: PR #280566 — Extension icon widget leaked 185 listeners after 37 toggles.

Step 2: One-Time Events

Rule: Use Event.once() for events that should only fire once (lifecycle events, close events, first-change events).

typescript
// BAD — listener stays registered forever after first firemodel.onDidDispose(() => store.dispose());
// GOOD — auto-removes after first invocationEvent.once(model.onDidDispose)(() => store.dispose());

Validated by: PRs #285657, #285661 — Terminal lifecycle hacks replaced with Event.once().

Step 3: Repeated Method Calls

Rule: Objects created in methods called multiple times must NOT be registered to the class this._register(). Use MutableDisposable or return IDisposable to the caller.

typescript
// BAD — every call adds another listener to the class storestartSearch() {    this._register(this.model.onResults(() => { ... }));}
// GOOD — MutableDisposable ensures max 1 listenerprivate readonly _searchListener = this._register(new MutableDisposable());
startSearch() {    this._searchListener.value = this.model.onResults(() => { ... });}

When the event should only fire once per method call, combine Event.once() with MutableDisposable — this auto-removes the listener after the first invocation while still guarding against repeated calls:

typescript
private readonly _searchListener = this._register(new MutableDisposable());
startSearch() {    this._searchListener.value = Event.once(this.model.onResults)(() => { ... });}

Validated by: PR #283466 — Terminal find widget leaked 1 listener per search.

Step 4: Model-Tied DisposableStores

Rule: When creating a DisposableStore tied to a model's lifetime, register model.onWillDispose(() => store.dispose()) to the store itself.

typescript
const store = new DisposableStore();store.add(model.onWillDispose(() => store.dispose()));store.add(model.onDidChange(() => { ... }));

Validated by: Pattern used in chatEditingSession.ts, fileBasedRecommendations.ts, testingContentProvider.ts.

Step 5: Resource Pool Patterns

Rule: When using factory methods that create pooled objects (lists, trees), disposables must be registered to the individual item, not the pool class.

typescript
// BAD — registers to pool, never cleaned per itemcreateItem() {    const item = new Item();    this._register(item.onEvent(() => { ... }));    return item;}
// GOOD — wrap with item-scoped disposalcreateItem(): IDisposable & Item {    const store = new DisposableStore();    const item = new Item();    store.add(item.onEvent(() => { ... }));    return { ...item, dispose: () => store.dispose() };}

Validated by: PR #290505 — Chat content parts CollapsibleListPool and TreePool leaked disposables.

Step 6: Test Validation

Rule: Every test suite that creates disposable objects must call ensureNoDisposablesAreLeakedInTestSuite().

typescript
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';
suite('MyFeature', () => {    ensureNoDisposablesAreLeakedInTestSuite();
    test('does something', () => {        // test disposables are tracked automatically    });});

Quick Reference

ScenarioPatternAnti-Pattern
DOM eventsaddDisposableListener().onclick =, addEventListener()
One-time eventsEvent.once(event)(handler)event(handler) for lifecycle
Repeated methodsMutableDisposable or return IDisposablethis._register() in non-constructor
Model lifecyclestore.add(model.onWillDispose(...))Forgetting cleanup
Pooled objectsItem-scoped DisposableStorePool-scoped this._register()
TestsensureNoDisposablesAreLeakedInTestSuite()No leak checking

Verification

After fixing leaks, verify by:

  1. Checking listener counts before/after repeated operations
  2. Running ensureNoDisposablesAreLeakedInTestSuite() in tests
  3. Confirming object counts stabilize (don't grow linearly with usage)
  4. For chat-specific leaks: Run the chat memory leak checker via npm run perf:chat-leak (see the chat-perf skill). It sends N messages in a single session, forces GC between each, and uses linear regression on heap/DOM samples to detect per-message growth. A slope above 2 MB/msg indicates a leak. Use --messages 20 --verbose for more accurate results.

來源與署名

來源:microsoft/vscode位於.github/skills/memory-leak-audit提交001f205

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架