Skip to content

Commit 4ae4068

Browse files
authored
[14f] potential listener LEAK detected, having 1063 listeners already. MOST frequent listener (851): (fix microsoft#249772) (microsoft#249857)
1 parent a119e40 commit 4ae4068

File tree

1 file changed

+67
-1
lines changed

1 file changed

+67
-1
lines changed

src/vs/workbench/services/textfile/common/textEditorService.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { IFileService } from '../../../../platform/files/common/files.js';
2323
import { IEditorResolverService, RegisteredEditorPriority } from '../../editor/common/editorResolverService.js';
2424
import { Disposable } from '../../../../base/common/lifecycle.js';
2525
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
26+
import { onUnexpectedError } from '../../../../base/common/errors.js';
2627

2728
export const ITextEditorService = createDecorator<ITextEditorService>('textEditorService');
2829

@@ -55,6 +56,16 @@ export interface ITextEditorService {
5556
resolveTextEditor(input: IUntypedFileEditorInput): Promise<IFileEditorInput>;
5657
}
5758

59+
class FileEditorInputLeakError extends Error {
60+
61+
constructor(message: string, stack: string) {
62+
super(message);
63+
64+
this.name = 'FileEditorInputLeakError';
65+
this.stack = stack;
66+
}
67+
}
68+
5869
export class TextEditorService extends Disposable implements ITextEditorService {
5970

6071
declare readonly _serviceBrand: undefined;
@@ -247,10 +258,65 @@ export class TextEditorService extends Disposable implements ITextEditorService
247258
// Otherwise create and add to cache
248259
input = factoryFn();
249260
this.editorInputCache.set(resource, input);
250-
Event.once(input.onWillDispose)(() => this.editorInputCache.delete(resource));
261+
262+
// Track Leaks
263+
const leakId = this.trackLeaks(input);
264+
265+
Event.once(input.onWillDispose)(() => {
266+
267+
// Remove from cache
268+
this.editorInputCache.delete(resource);
269+
270+
// Untrack Leaks
271+
if (leakId) {
272+
this.untrackLeaks(leakId);
273+
}
274+
});
251275

252276
return input;
253277
}
278+
279+
//#region Leak Monitoring
280+
281+
private static readonly LEAK_TRACKING_THRESHOLD = 256;
282+
private static readonly LEAK_REPORTING_THRESHOLD = 2 * this.LEAK_TRACKING_THRESHOLD;
283+
private static LEAK_REPORTED = false;
284+
285+
private readonly mapLeakToCounter = new Map<string, number>();
286+
287+
private trackLeaks(input: TextResourceEditorInput | IFileEditorInput | UntitledTextEditorInput): string | undefined {
288+
if (TextEditorService.LEAK_REPORTED || this.editorInputCache.size < TextEditorService.LEAK_TRACKING_THRESHOLD) {
289+
return undefined;
290+
}
291+
292+
const leakId = `${input.resource.scheme}#${input.typeId || '<no typeId>'}#${input.editorId || '<no editorId>'}\n${new Error().stack?.split('\n').slice(2).join('\n') ?? ''}`;
293+
const leakCounter = (this.mapLeakToCounter.get(leakId) ?? 0) + 1;
294+
this.mapLeakToCounter.set(leakId, leakCounter);
295+
296+
if (this.editorInputCache.size > TextEditorService.LEAK_REPORTING_THRESHOLD) {
297+
TextEditorService.LEAK_REPORTED = true;
298+
299+
const [topLeak, topCount] = Array.from(this.mapLeakToCounter.entries()).reduce(
300+
([topLeak, topCount], [key, val]) => val > topCount ? [key, val] : [topLeak, topCount]
301+
);
302+
303+
const message = `Potential text editor input LEAK detected, having ${this.editorInputCache.size} text editor inputs already. Most frequent owner (${topCount})`;
304+
onUnexpectedError(new FileEditorInputLeakError(message, topLeak));
305+
}
306+
307+
return leakId;
308+
}
309+
310+
private untrackLeaks(leakId: string): void {
311+
const stackCounter = (this.mapLeakToCounter.get(leakId) ?? 1) - 1;
312+
this.mapLeakToCounter.set(leakId, stackCounter);
313+
314+
if (stackCounter === 0) {
315+
this.mapLeakToCounter.delete(leakId);
316+
}
317+
}
318+
319+
//#endregion
254320
}
255321

256322
registerSingleton(ITextEditorService, TextEditorService, InstantiationType.Eager /* do not change: https://github.com/microsoft/vscode/issues/137675 */);

0 commit comments

Comments
 (0)