mcpField
M
memory-leak-audit

memory-leak-audit

github.com/microsoft测试与质量194.4k3.2k 次安装今天更新
代码仓库

关于 memory-leak-audit

用于审查和预防 VS Code 代码内存泄漏的技能,通过生命周期、事件监听器、可释放对象、对象池和测试检查,排查重复操作与对象清理问题。

主要功能

  1. 01审查 DOM 事件监听器注册
  2. 02为生命周期事件使用一次性处理器
  3. 03用 MutableDisposable 管理重复订阅
  4. 04将可释放存储绑定到模型生命周期
  5. 05为对象池中的单项管理清理
  6. 06检查测试中的可释放对象泄漏

适用场景

  1. 01审查注册事件或 DOM 处理器的代码
  2. 02调查监听器数量持续增长的问题
  3. 03检查重复调用方法中的清理逻辑
  4. 04验证测试套件中的可释放对象清理

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/microsoft/vscode --skill memory-leak-audit

安装后在 Claude Code 中输入/memory-leak-audit确认已加载。

详细介绍

内存泄漏审计

这是 VS Code 中排名第一的 Bug 类别。本技能总结了预防和修复泄漏的模式。

适用场景

  • 审查注册事件监听器或 DOM 处理器的代码
  • 修复已报告的内存泄漏(监听器数量随时间增长)
  • 在会被重复调用的方法中创建对象
  • 处理模型生命周期事件(onWillDispose、onDidClose)
  • 在构造函数或设置方法中添加事件订阅

审计清单

按顺序逐项检查。只要漏掉一种模式,就可能导致数千个对象泄漏。

步骤 1:DOM 事件监听器

规则:绝不要直接使用原始的 .onload、.onclick 或 addEventListener()。始终使用 addDisposableListener()。

// BAD — leaks a listener every call
this.iconElement.onload = () => { ... };

// GOOD — tracked and disposable
this._register(addDisposableListener(this.iconElement, 'load', () => { ... }));

验证依据:PR #280566 —— 扩展图标组件在切换 37 次后泄漏了 185 个监听器。

步骤 2:一次性事件

规则:对于只应触发一次的事件(生命周期事件、关闭事件、首次更改事件),请使用 Event.once()。

// BAD — listener stays registered forever after first fire
model.onDidDispose(() => store.dispose());

// GOOD — auto-removes after first invocation
Event.once(model.onDidDispose)(() => store.dispose());

验证依据:PR #285657、#285661 —— 终端生命周期相关的变通方案已替换为 Event.once()。

步骤 3:重复调用的方法

规则:在会被多次调用的方法中创建的对象,绝不能注册到类的 this._register()。请使用 MutableDisposable,或将 IDisposable 返回给调用方。

// BAD — every call adds another listener to the class store
startSearch() {
    this._register(this.model.onResults(() => { ... }));
}

// GOOD — MutableDisposable ensures max 1 listener
private readonly _searchListener = this._register(new MutableDisposable());

startSearch() {
    this._searchListener.value = this.model.onResults(() => { ... });
}

如果事件在每次方法调用中只应触发一次,请将 Event.once() 与 MutableDisposable 结合使用——这样会在首次调用后自动移除监听器,同时防止重复调用造成问题:

private readonly _searchListener = this._register(new MutableDisposable());

startSearch() {
    this._searchListener.value = Event.once(this.model.onResults)(() => { ... });
}

验证依据:PR #283466 —— 终端查找组件每次搜索都会泄漏 1 个监听器。

步骤 4:与模型绑定的 DisposableStore

规则:创建与模型生命周期绑定的 DisposableStore 时,请将 model.onWillDispose(() => store.dispose()) 注册到该 store 自身。

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

验证依据:此模式用于 chatEditingSession.ts、fileBasedRecommendations.ts、testingContentProvider.ts。

步骤 5:资源池模式

规则:使用创建池化对象(列表、树)的工厂方法时,必须将可释放对象注册到单个项,而不是池类。

// BAD — registers to pool, never cleaned per item
createItem() {
    const item = new Item();
    this._register(item.onEvent(() => { ... }));
    return item;
}

// GOOD — wrap with item-scoped disposal
createItem(): IDisposable & Item {
    const store = new DisposableStore();
    const item = new Item();
    store.add(item.onEvent(() => { ... }));
    return { ...item, dispose: () => store.dispose() };
}

验证依据:PR #290505 —— Chat 内容部件中的 CollapsibleListPool 和 TreePool 泄漏了可释放对象。

步骤 6:测试验证

规则:每个创建可释放对象的测试套件都必须调用 ensureNoDisposablesAreLeakedInTestSuite()。

import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';

suite('MyFeature', () => {
    ensureNoDisposablesAreLeakedInTestSuite();

    test('does something', () => {
        // test disposables are tracked automatically
    });
});

快速参考

场景模式反模式
DOM 事件addDisposableListener().onclick =、addEventListener()
一次性事件Event.once(event)(handler)生命周期事件使用 event(handler)
重复调用的方法MutableDisposable 或返回 IDisposable在非构造函数中使用 this._register()
模型生命周期store.add(model.onWillDispose(...))忘记清理
池化对象作用域限定于单个项的 DisposableStore作用域限定于池的 this._register()
测试ensureNoDisposablesAreLeakedInTestSuite()不检查泄漏

验证

修复泄漏后,请通过以下方式验证:

  1. 检查重复操作前后的监听器数量
  2. 在测试中运行 ensureNoDisposablesAreLeakedInTestSuite()
  3. 确认对象数量趋于稳定(不会随使用量线性增长)
  4. 对于 Chat 特有的泄漏:通过 npm run perf:chat-leak 运行 Chat 内存泄漏检查器(参见 chat-perf 技能)。它会在单个会话中发送 N 条消息,在每条消息之间强制执行 GC,并对堆和 DOM 采样数据进行线性回归,以检测每条消息导致的增长。斜率超过 2 MB/msg 表示存在泄漏。使用 --messages 20 --verbose 可获得更准确的结果。

评论

支持 Markdown