mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge from master
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user