Merge from master

This commit is contained in:
Raj Musuku
2019-02-21 17:56:04 -08:00
parent 5a146e34fa
commit 666ae11639
11482 changed files with 119352 additions and 255574 deletions

View File

@@ -2,13 +2,11 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { onUnexpectedError } from 'vs/base/common/errors';
import { once as onceFn } from 'vs/base/common/functional';
import { combinedDisposable, Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { LinkedList } from 'vs/base/common/linkedList';
import { TPromise } from 'vs/base/common/winjs.base';
/**
* To an event a function with one or zero parameters
@@ -30,6 +28,79 @@ 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): undefined | (() => void) {
let threshold = _globalLeakWarningThreshold;
if (typeof this.customThreshold === 'number') {
threshold = this.customThreshold;
}
if (threshold <= 0 || listenerCount < threshold) {
return undefined;
}
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);
this._stacks.set(stack, count + 1);
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!);
}
return () => {
let count = (this._stacks!.get(stack) || 0);
this._stacks!.set(stack, count - 1);
};
}
}
/**
@@ -57,13 +128,18 @@ export class Emitter<T> {
private static readonly _noop = function () { };
private _event: Event<T>;
private _disposed: boolean;
private _deliveryQueue: [Listener, T][];
protected _listeners: LinkedList<Listener>;
constructor(private _options?: EmitterOptions) {
private readonly _options: EmitterOptions | undefined;
private readonly _leakageMon: LeakageMonitor | undefined;
private _disposed: boolean = false;
private _event: Event<T> | undefined;
private _deliveryQueue: [Listener, (T | undefined)][] | undefined;
protected _listeners: LinkedList<Listener> | undefined;
constructor(options?: EmitterOptions) {
this._options = options;
this._leakageMon = _globalLeakWarningThreshold > 0
? new LeakageMonitor(this._options && this._options.leakWarningThreshold)
: undefined;
}
/**
@@ -93,14 +169,26 @@ export class Emitter<T> {
this._options.onListenerDidAdd(this, listener, thisArgs);
}
// check and record this emitter for potential leakage
let removeMonitor: (() => void) | undefined;
if (this._leakageMon) {
removeMonitor = this._leakageMon.check(this._listeners.size);
}
let result: IDisposable;
result = {
dispose: () => {
if (removeMonitor) {
removeMonitor();
}
result.dispose = Emitter._noop;
if (!this._disposed) {
remove();
if (this._options && this._options.onLastListenerRemove && this._listeners.isEmpty()) {
this._options.onLastListenerRemove(this);
if (this._options && this._options.onLastListenerRemove) {
const hasListeners = (this._listeners && !this._listeners.isEmpty());
if (!hasListeners) {
this._options.onLastListenerRemove(this);
}
}
}
}
@@ -134,7 +222,7 @@ export class Emitter<T> {
}
while (this._deliveryQueue.length > 0) {
const [listener, event] = this._deliveryQueue.shift();
const [listener, event] = this._deliveryQueue.shift()!;
try {
if (typeof listener === 'function') {
listener.call(undefined, event);
@@ -155,6 +243,9 @@ export class Emitter<T> {
if (this._deliveryQueue) {
this._deliveryQueue.length = 0;
}
if (this._leakageMon) {
this._leakageMon.dispose();
}
this._disposed = true;
}
}
@@ -185,7 +276,7 @@ export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
}
while (this._asyncDeliveryQueue.length > 0) {
const [listener, event, thenables] = this._asyncDeliveryQueue.shift();
const [listener, event, thenables] = this._asyncDeliveryQueue.shift()!;
try {
if (typeof listener === 'function') {
listener.call(undefined, event);
@@ -209,7 +300,7 @@ export class EventMultiplexer<T> implements IDisposable {
private readonly emitter: Emitter<T>;
private hasListeners = false;
private events: { event: Event<T>; listener: IDisposable; }[] = [];
private events: { event: Event<T>; listener: IDisposable | null; }[] = [];
constructor() {
this.emitter = new Emitter<T>({
@@ -252,12 +343,14 @@ export class EventMultiplexer<T> implements IDisposable {
this.events.forEach(e => this.unhook(e));
}
private hook(e: { event: Event<T>; listener: IDisposable; }): void {
private hook(e: { event: Event<T>; listener: IDisposable | null; }): void {
e.listener = e.event(r => this.emitter.fire(r));
}
private unhook(e: { event: Event<T>; listener: IDisposable; }): void {
e.listener.dispose();
private unhook(e: { event: Event<T>; listener: IDisposable | null; }): void {
if (e.listener) {
e.listener.dispose();
}
e.listener = null;
}
@@ -266,23 +359,12 @@ export class EventMultiplexer<T> implements IDisposable {
}
}
export function fromCallback<T>(fn: (handler: (e: T) => void) => IDisposable): Event<T> {
let listener: IDisposable;
const emitter = new Emitter<T>({
onFirstListenerAdd: () => listener = fn(e => emitter.fire(e)),
onLastListenerRemove: () => listener.dispose()
});
return emitter.event;
}
export function fromPromise<T =any>(promise: TPromise<T>): Event<T> {
export function fromPromise<T =any>(promise: Thenable<T>): Event<T> {
const emitter = new Emitter<T>();
let shouldEmit = false;
promise
.then(null, () => null)
.then(undefined, () => null)
.then(() => {
if (!shouldEmit) {
setTimeout(() => emitter.fire(), 0);
@@ -295,22 +377,31 @@ export function fromPromise<T =any>(promise: TPromise<T>): Event<T> {
return emitter.event;
}
export function toPromise<T>(event: Event<T>): TPromise<T> {
return new TPromise(complete => {
const sub = event(e => {
sub.dispose();
complete(e);
});
});
export function toPromise<T>(event: Event<T>): Thenable<T> {
return new Promise(c => once(event)(c));
}
export function once<T>(event: Event<T>): Event<T> {
return (listener, thisArgs = null, disposables?) => {
// we need this, in case the event fires during the listener call
let didFire = false;
const result = event(e => {
result.dispose();
if (didFire) {
return;
} else if (result) {
result.dispose();
} else {
didFire = true;
}
return listener.call(thisArgs, e);
}, null, disposables);
if (didFire) {
result.dispose();
}
return result;
};
}
@@ -319,16 +410,17 @@ export function anyEvent<T>(...events: Event<T>[]): Event<T> {
return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
}
export function debounceEvent<T>(event: Event<T>, merger: (last: T, event: T) => T, delay?: number, leading?: boolean): Event<T>;
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I) => O, delay?: number, leading?: boolean): Event<O>;
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I) => O, delay: number = 100, leading = false): Event<O> {
export function debounceEvent<T>(event: Event<T>, merger: (last: T, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<T>;
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<O>;
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number): Event<O> {
let subscription: IDisposable;
let output: O = undefined;
let output: O | undefined = undefined;
let handle: any = undefined;
let numDebouncedCalls = 0;
const emitter = new Emitter<O>({
leakWarningThreshold,
onFirstListenerAdd() {
subscription = event(cur => {
numDebouncedCalls++;
@@ -360,7 +452,7 @@ export function debounceEvent<I, O>(event: Event<I>, merger: (last: O, event: I)
}
/**
* The EventDelayer is useful in situations in which you want
* The EventBufferer is useful in situations in which you want
* to delay firing your events during some code.
* You can wrap that code and be sure that the event will not
* be fired during that wrap.
@@ -397,12 +489,13 @@ export class EventBufferer {
};
}
bufferEvents(fn: () => void): void {
bufferEvents<R = void>(fn: () => R): R {
const buffer: Function[] = [];
this.buffers.push(buffer);
fn();
const r = fn();
this.buffers.pop();
buffer.forEach(flush => flush());
return r;
}
}
@@ -430,6 +523,10 @@ export function filterEvent<T>(event: Event<T>, filter: (e: T) => boolean): Even
return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables);
}
export function signalEvent<T>(event: Event<T>): Event<void> {
return event as Event<any> as Event<void>;
}
class ChainableEvent<T> implements IChainableEvent<T> {
get event(): Event<T> { return this._event; }
@@ -492,10 +589,10 @@ export function stopwatch<T>(event: Event<T>): Event<number> {
* // 4
* ```
*/
export function buffer<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Event<T> {
buffer = buffer.slice();
export function buffer<T>(event: Event<T>, nextTick = false, _buffer: T[] = []): Event<T> {
let buffer: T[] | null = _buffer.slice();
let listener = event(e => {
let listener: IDisposable | null = event(e => {
if (buffer) {
buffer.push(e);
} else {
@@ -504,7 +601,9 @@ export function buffer<T>(event: Event<T>, nextTick = false, buffer: T[] = []):
});
const flush = () => {
buffer.forEach(e => emitter.fire(e));
if (buffer) {
buffer.forEach(e => emitter.fire(e));
}
buffer = null;
};
@@ -526,7 +625,9 @@ export function buffer<T>(event: Event<T>, nextTick = false, buffer: T[] = []):
},
onLastListenerRemove() {
listener.dispose();
if (listener) {
listener.dispose();
}
listener = null;
}
});
@@ -563,18 +664,34 @@ export function echo<T>(event: Event<T>, nextTick = false, buffer: T[] = []): Ev
export class Relay<T> implements IDisposable {
private emitter = new Emitter<T>();
private listening = false;
private inputEvent: Event<T> = Event.None;
private inputEventListener: IDisposable = Disposable.None;
private emitter = new Emitter<T>({
onFirstListenerDidAdd: () => {
this.listening = true;
this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter);
},
onLastListenerRemove: () => {
this.listening = false;
this.inputEventListener.dispose();
}
});
readonly event: Event<T> = this.emitter.event;
private disposable: IDisposable = Disposable.None;
set input(event: Event<T>) {
this.disposable.dispose();
this.disposable = event(this.emitter.fire, this.emitter);
this.inputEvent = event;
if (this.listening) {
this.inputEventListener.dispose();
this.inputEventListener = event(this.emitter.fire, this.emitter);
}
}
dispose() {
this.disposable.dispose();
this.inputEventListener.dispose();
this.emitter.dispose();
}
}