Skip to content

Commit

Permalink
event - add leakage warning threshold logic and configure for renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
jrieken committed Nov 13, 2018
1 parent fc3083d commit b548ced
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 12 deletions.
88 changes: 78 additions & 10 deletions src/vs/base/common/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,71 @@ export interface EmitterOptions {
onFirstListenerDidAdd?: Function;
onListenerDidAdd?: Function;
onLastListenerRemove?: Function;
leakWarningThreshold?: number;
}

let _globalLeakWarningThreshold = -1;
export function setGlobalLeakWarningThreshold(n: number): IDisposable {
let oldValue = _globalLeakWarningThreshold;
_globalLeakWarningThreshold = n;
return {
dispose() {
_globalLeakWarningThreshold = oldValue;
}
};
}

class LeakageMonitor {

private _stacks: Map<string, number> | undefined;
private _warnCountdown: number = 0;

constructor(
readonly customThreshold?: number,
readonly name: string = Math.random().toString(18).slice(2, 5),
) { }

dispose(): void {
if (this._stacks) {
this._stacks.clear();
}
}

check(listenerCount: number): void {

let threshold = _globalLeakWarningThreshold;
if (typeof this.customThreshold === 'number') {
threshold = this.customThreshold;
}
if (threshold > 1 && threshold < listenerCount) {
if (!this._stacks) {
this._stacks = new Map();
}
let stack = new Error().stack!.split('\n').slice(3).join('\n');
let count = (this._stacks.get(stack) || 0) + 1;
this._stacks.set(stack, count);
this._warnCountdown -= 1;

if (this._warnCountdown <= 0) {
// only warn on first exceed and then every time the limit
// is exceeded by 50% again
this._warnCountdown = threshold * .5;

// find most frequent listener and print warning
let topStack: string;
let topCount: number = 0;
this._stacks.forEach((count, stack) => {
if (!topStack || topCount < count) {
topStack = stack;
topCount = count;
}
});

console.warn(`[${this.name}] potential listener LEAK detected, having ${listenerCount} listeners already. MOST frequent listener (${topCount}):`);
console.warn(topStack!);
}
}
}
}

/**
Expand Down Expand Up @@ -55,16 +120,15 @@ export class Emitter<T> {

private static readonly _noop = function () { };

private _event: Event<T> | null;
private _disposed: boolean;
private _deliveryQueue: [Listener, (T | undefined)][] | null;
protected _listeners: LinkedList<Listener> | null;
private readonly _options: EmitterOptions | undefined;
private readonly _leakageMon: LeakageMonitor = new LeakageMonitor();
private _disposed: boolean = false;
private _event: Event<T> | undefined;
private _deliveryQueue: [Listener, (T | undefined)][] | undefined;
protected _listeners: LinkedList<Listener> | undefined;

constructor(private _options: EmitterOptions | null = null) {
this._event = null;
this._disposed = false;
this._deliveryQueue = null;
this._listeners = null;
constructor(options?: EmitterOptions) {
this._options = options;
}

/**
Expand Down Expand Up @@ -94,6 +158,9 @@ export class Emitter<T> {
this._options.onListenerDidAdd(this, listener, thisArgs);
}

// check and record this emitter for potential leakage
this._leakageMon.check(this._listeners.size);

let result: IDisposable;
result = {
dispose: () => {
Expand Down Expand Up @@ -154,11 +221,12 @@ export class Emitter<T> {

dispose() {
if (this._listeners) {
this._listeners = null;
this._listeners = undefined;
}
if (this._deliveryQueue) {
this._deliveryQueue.length = 0;
}
this._leakageMon.dispose();
this._disposed = true;
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/vs/base/common/linkedList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export class LinkedList<E> {

private _first: Node<E> | undefined;
private _last: Node<E> | undefined;
private _size: number = 0;

get size(): number {
return this._size;
}

isEmpty(): boolean {
return !this._first;
Expand Down Expand Up @@ -57,6 +62,7 @@ export class LinkedList<E> {
newNode.next = oldFirst;
oldFirst.prev = newNode;
}
this._size += 1;

return () => {
let candidate: Node<E> | undefined = this._first;
Expand Down Expand Up @@ -88,6 +94,7 @@ export class LinkedList<E> {
}

// done
this._size -= 1;
break;
}
};
Expand Down
8 changes: 6 additions & 2 deletions src/vs/base/test/common/linkedList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@ import { LinkedList } from 'vs/base/common/linkedList';
suite('LinkedList', function () {

function assertElements<E>(list: LinkedList<E>, ...elements: E[]) {
// first: assert toArray

// check size
assert.equal(list.size, elements.length);

// assert toArray
assert.deepEqual(list.toArray(), elements);

// second: assert iterator
// assert iterator
for (let iter = list.iterator(), element = iter.next(); !element.done; element = iter.next()) {
assert.equal(elements.shift(), element.value);
}
Expand Down
4 changes: 4 additions & 0 deletions src/vs/workbench/electron-browser/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { parseStorage, StorageObject } from 'vs/platform/storage/common/storageL
import { StorageScope } from 'vs/platform/storage/common/storage';
import { endsWith } from 'vs/base/common/strings';
import { IdleValue } from 'vs/base/common/async';
import { setGlobalLeakWarningThreshold } from 'vs/base/common/event';

gracefulFs.gracefulify(fs); // enable gracefulFs

Expand All @@ -61,6 +62,9 @@ export function startup(configuration: IWindowConfiguration): Promise<void> {
// Setup perf
perf.importEntries(configuration.perfEntries);

// Configure emitter leak warning threshold
setGlobalLeakWarningThreshold(500);

// Browser config
browser.setZoomFactor(webFrame.getZoomFactor()); // Ensure others can listen to zoom level changes
browser.setZoomLevel(webFrame.getZoomLevel(), true /* isTrusted */); // Can be trusted because we are not setting it ourselves (https://github.com/Microsoft/vscode/issues/26151)
Expand Down

0 comments on commit b548ced

Please sign in to comment.