mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
Merge vscode 1.67 (#20883)
* Fix initial build breaks from 1.67 merge (#2514) * Update yarn lock files * Update build scripts * Fix tsconfig * Build breaks * WIP * Update yarn lock files * Misc breaks * Updates to package.json * Breaks * Update yarn * Fix breaks * Breaks * Build breaks * Breaks * Breaks * Breaks * Breaks * Breaks * Missing file * Breaks * Breaks * Breaks * Breaks * Breaks * Fix several runtime breaks (#2515) * Missing files * Runtime breaks * Fix proxy ordering issue * Remove commented code * Fix breaks with opening query editor * Fix post merge break * Updates related to setup build and other breaks (#2516) * Fix bundle build issues * Update distro * Fix distro merge and update build JS files * Disable pipeline steps * Remove stats call * Update license name * Make new RPM dependencies a warning * Fix extension manager version checks * Update JS file * Fix a few runtime breaks * Fixes * Fix runtime issues * Fix build breaks * Update notebook tests (part 1) * Fix broken tests * Linting errors * Fix hygiene * Disable lint rules * Bump distro * Turn off smoke tests * Disable integration tests * Remove failing "activate" test * Remove failed test assertion * Disable other broken test * Disable query history tests * Disable extension unit tests * Disable failing tasks
This commit is contained in:
@@ -6,10 +6,25 @@
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { once as onceFn } from 'vs/base/common/functional';
|
||||
import { combinedDisposable, Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { combinedDisposable, Disposable, DisposableStore, IDisposable, SafeDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { StopWatch } from 'vs/base/common/stopwatch';
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------------
|
||||
// Uncomment the next line to print warnings whenever an emitter with listeners is disposed. That is a sign of code smell.
|
||||
// -----------------------------------------------------------------------------------------------------------------------
|
||||
let _enableDisposeWithListenerWarning = false;
|
||||
// _enableDisposeWithListenerWarning = Boolean("TRUE"); // causes a linter warning so that it cannot be pushed
|
||||
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------------
|
||||
// Uncomment the next line to print warnings whenever a snapshotted event is used repeatedly without cleanup.
|
||||
// See https://github.com/microsoft/vscode/issues/142851
|
||||
// -----------------------------------------------------------------------------------------------------------------------
|
||||
let _enableSnapshotPotentialLeakWarning = false;
|
||||
// _enableSnapshotPotentialLeakWarning = Boolean("TRUE"); // causes a linter warning so that it cannot be pushed
|
||||
|
||||
/**
|
||||
* To an event a function with one or zero parameters
|
||||
* can be subscribed. The event is the subscriber function itself.
|
||||
@@ -21,6 +36,23 @@ export interface Event<T> {
|
||||
export namespace Event {
|
||||
export const None: Event<any> = () => Disposable.None;
|
||||
|
||||
|
||||
function _addLeakageTraceLogic(options: EmitterOptions) {
|
||||
if (_enableSnapshotPotentialLeakWarning) {
|
||||
const { onListenerDidAdd: origListenerDidAdd } = options;
|
||||
const stack = Stacktrace.create();
|
||||
let count = 0;
|
||||
options.onListenerDidAdd = () => {
|
||||
if (++count === 2) {
|
||||
console.warn('snapshotted emitter LIKELY used public and SHOULD HAVE BEEN created with DisposableStore. snapshotted here');
|
||||
stack.print();
|
||||
}
|
||||
origListenerDidAdd?.();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Given an event, returns another event which only fires once.
|
||||
*/
|
||||
@@ -50,27 +82,33 @@ export namespace Event {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function map<I, O>(event: Event<I>, map: (i: I) => O): Event<O> {
|
||||
return snapshot((listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables));
|
||||
export function map<I, O>(event: Event<I>, map: (i: I) => O, disposable?: DisposableStore): Event<O> {
|
||||
return snapshot((listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables), disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function forEach<I>(event: Event<I>, each: (i: I) => void): Event<I> {
|
||||
return snapshot((listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables));
|
||||
export function forEach<I>(event: Event<I>, each: (i: I) => void, disposable?: DisposableStore): Event<I> {
|
||||
return snapshot((listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables), disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function filter<T, U>(event: Event<T | U>, filter: (e: T | U) => e is T): Event<T>;
|
||||
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T>;
|
||||
export function filter<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R): Event<R>;
|
||||
export function filter<T>(event: Event<T>, filter: (e: T) => boolean): Event<T> {
|
||||
return snapshot((listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables));
|
||||
export function filter<T, U>(event: Event<T | U>, filter: (e: T | U) => e is T, disposable?: DisposableStore): Event<T>;
|
||||
export function filter<T>(event: Event<T>, filter: (e: T) => boolean, disposable?: DisposableStore): Event<T>;
|
||||
export function filter<T, R>(event: Event<T | R>, filter: (e: T | R) => e is R, disposable?: DisposableStore): Event<R>;
|
||||
export function filter<T>(event: Event<T>, filter: (e: T) => boolean, disposable?: DisposableStore): Event<T> {
|
||||
return snapshot((listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables), disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,53 +129,65 @@ export namespace Event {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function reduce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, initial?: O): Event<O> {
|
||||
export function reduce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, initial?: O, disposable?: DisposableStore): Event<O> {
|
||||
let output: O | undefined = initial;
|
||||
|
||||
return map<I, O>(event, e => {
|
||||
output = merge(output, e);
|
||||
return output;
|
||||
});
|
||||
}, disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
*/
|
||||
function snapshot<T>(event: Event<T>): Event<T> {
|
||||
function snapshot<T>(event: Event<T>, disposable: DisposableStore | undefined): Event<T> {
|
||||
let listener: IDisposable;
|
||||
const emitter = new Emitter<T>({
|
||||
|
||||
const options: EmitterOptions | undefined = {
|
||||
onFirstListenerAdd() {
|
||||
listener = event(emitter.fire, emitter);
|
||||
},
|
||||
onLastListenerRemove() {
|
||||
listener.dispose();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!disposable) {
|
||||
_addLeakageTraceLogic(options);
|
||||
}
|
||||
|
||||
const emitter = new Emitter<T>(options);
|
||||
|
||||
if (disposable) {
|
||||
disposable.add(emitter);
|
||||
}
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function debounce<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<T>;
|
||||
export function debounce<T>(event: Event<T>, merge: (last: T | undefined, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<T>;
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<O>;
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
*/
|
||||
export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number): Event<O> {
|
||||
export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O>;
|
||||
|
||||
export function debounce<I, O>(event: Event<I>, merge: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number, disposable?: DisposableStore): Event<O> {
|
||||
|
||||
let subscription: IDisposable;
|
||||
let output: O | undefined = undefined;
|
||||
let handle: any = undefined;
|
||||
let numDebouncedCalls = 0;
|
||||
|
||||
const emitter = new Emitter<O>({
|
||||
const options: EmitterOptions | undefined = {
|
||||
leakWarningThreshold,
|
||||
onFirstListenerAdd() {
|
||||
subscription = event(cur => {
|
||||
@@ -165,15 +215,27 @@ export namespace Event {
|
||||
onLastListenerRemove() {
|
||||
subscription.dispose();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
if (!disposable) {
|
||||
_addLeakageTraceLogic(options);
|
||||
}
|
||||
|
||||
const emitter = new Emitter<O>(options);
|
||||
|
||||
if (disposable) {
|
||||
disposable.add(emitter);
|
||||
}
|
||||
|
||||
return emitter.event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function latch<T>(event: Event<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b): Event<T> {
|
||||
export function latch<T>(event: Event<T>, equals: (a: T, b: T) => boolean = (a, b) => a === b, disposable?: DisposableStore): Event<T> {
|
||||
let firstCall = true;
|
||||
let cache: T;
|
||||
|
||||
@@ -182,23 +244,27 @@ export namespace Event {
|
||||
firstCall = false;
|
||||
cache = value;
|
||||
return shouldEmit;
|
||||
});
|
||||
}, disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function split<T, U>(event: Event<T | U>, isT: (e: T | U) => e is T): [Event<T>, Event<U>] {
|
||||
export function split<T, U>(event: Event<T | U>, isT: (e: T | U) => e is T, disposable?: DisposableStore): [Event<T>, Event<U>] {
|
||||
return [
|
||||
Event.filter(event, isT),
|
||||
Event.filter(event, e => !isT(e)) as Event<U>,
|
||||
Event.filter(event, isT, disposable),
|
||||
Event.filter(event, e => !isT(e), disposable) as Event<U>,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated DO NOT use, this leaks memory
|
||||
* *NOTE* that this function returns an `Event` and it MUST be called with a `DisposableStore` whenever the returned
|
||||
* event is accessible to "third parties", e.g the event is a public property. Otherwise a leaked listener on the
|
||||
* returned event causes this utility to leak a listener on the original event.
|
||||
*/
|
||||
export function buffer<T>(event: Event<T>, nextTick = false, _buffer: T[] = []): Event<T> {
|
||||
export function buffer<T>(event: Event<T>, flushAfterTimeout = false, _buffer: T[] = []): Event<T> {
|
||||
let buffer: T[] | null = _buffer.slice();
|
||||
|
||||
let listener: IDisposable | null = event(e => {
|
||||
@@ -225,7 +291,7 @@ export namespace Event {
|
||||
|
||||
onFirstListenerDidAdd() {
|
||||
if (buffer) {
|
||||
if (nextTick) {
|
||||
if (flushAfterTimeout) {
|
||||
setTimeout(flush);
|
||||
} else {
|
||||
flush();
|
||||
@@ -245,6 +311,7 @@ export namespace Event {
|
||||
}
|
||||
|
||||
export interface IChainableEvent<T> {
|
||||
|
||||
event: Event<T>;
|
||||
map<O>(fn: (i: T) => O): IChainableEvent<O>;
|
||||
forEach(fn: (i: T) => void): IChainableEvent<T>;
|
||||
@@ -337,9 +404,29 @@ export namespace Event {
|
||||
export function toPromise<T>(event: Event<T>): Promise<T> {
|
||||
return new Promise(resolve => once(event)(resolve));
|
||||
}
|
||||
}
|
||||
|
||||
export type Listener<T> = [(e: T) => void, any] | ((e: T) => void);
|
||||
export function runAndSubscribe<T>(event: Event<T>, handler: (e: T | undefined) => any): IDisposable {
|
||||
handler(undefined);
|
||||
return event(e => handler(e));
|
||||
}
|
||||
|
||||
export function runAndSubscribeWithStore<T>(event: Event<T>, handler: (e: T | undefined, disposableStore: DisposableStore) => any): IDisposable {
|
||||
let store: DisposableStore | null = null;
|
||||
|
||||
function run(e: T | undefined) {
|
||||
store?.dispose();
|
||||
store = new DisposableStore();
|
||||
handler(e, store);
|
||||
}
|
||||
|
||||
run(undefined);
|
||||
const disposable = event(e => run(e));
|
||||
return toDisposable(() => {
|
||||
disposable.dispose();
|
||||
store?.dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface EmitterOptions {
|
||||
onFirstListenerAdd?: Function;
|
||||
@@ -348,8 +435,14 @@ export interface EmitterOptions {
|
||||
onLastListenerRemove?: Function;
|
||||
leakWarningThreshold?: number;
|
||||
|
||||
/**
|
||||
* Pass in a delivery queue, which is useful for ensuring
|
||||
* in order event delivery across multiple emitters.
|
||||
*/
|
||||
deliveryQueue?: EventDeliveryQueue;
|
||||
|
||||
/** ONLY enable this during development */
|
||||
_profName?: string
|
||||
_profName?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -411,7 +504,7 @@ class LeakageMonitor {
|
||||
}
|
||||
}
|
||||
|
||||
check(listenerCount: number): undefined | (() => void) {
|
||||
check(stack: Stacktrace, listenerCount: number): undefined | (() => void) {
|
||||
|
||||
let threshold = _globalLeakWarningThreshold;
|
||||
if (typeof this.customThreshold === 'number') {
|
||||
@@ -425,9 +518,8 @@ class LeakageMonitor {
|
||||
if (!this._stacks) {
|
||||
this._stacks = new Map();
|
||||
}
|
||||
const stack = new Error().stack!.split('\n').slice(3).join('\n');
|
||||
const count = (this._stacks.get(stack) || 0);
|
||||
this._stacks.set(stack, count + 1);
|
||||
const count = (this._stacks.get(stack.value) || 0);
|
||||
this._stacks.set(stack.value, count + 1);
|
||||
this._warnCountdown -= 1;
|
||||
|
||||
if (this._warnCountdown <= 0) {
|
||||
@@ -450,12 +542,40 @@ class LeakageMonitor {
|
||||
}
|
||||
|
||||
return () => {
|
||||
const count = (this._stacks!.get(stack) || 0);
|
||||
this._stacks!.set(stack, count - 1);
|
||||
const count = (this._stacks!.get(stack.value) || 0);
|
||||
this._stacks!.set(stack.value, count - 1);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class Stacktrace {
|
||||
|
||||
static create() {
|
||||
return new Stacktrace(new Error().stack ?? '');
|
||||
}
|
||||
|
||||
private constructor(readonly value: string) { }
|
||||
|
||||
print() {
|
||||
console.warn(this.value.split('\n').slice(2).join('\n'));
|
||||
}
|
||||
}
|
||||
|
||||
class Listener<T> {
|
||||
|
||||
readonly subscription = new SafeDisposable();
|
||||
|
||||
constructor(
|
||||
readonly callback: (e: T) => void,
|
||||
readonly callbackThis: any | undefined,
|
||||
readonly stack: Stacktrace | undefined
|
||||
) { }
|
||||
|
||||
invoke(e: T) {
|
||||
this.callback.call(this.callbackThis, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The Emitter can be used to expose an Event to the public
|
||||
* to fire it from the insides.
|
||||
@@ -478,18 +598,55 @@ class LeakageMonitor {
|
||||
}
|
||||
*/
|
||||
export class Emitter<T> {
|
||||
|
||||
private readonly _options?: EmitterOptions;
|
||||
private readonly _leakageMon?: LeakageMonitor;
|
||||
private readonly _perfMon?: EventProfiling;
|
||||
private _disposed: boolean = false;
|
||||
private _event?: Event<T>;
|
||||
private _deliveryQueue?: LinkedList<[Listener<T>, T]>;
|
||||
private _deliveryQueue?: EventDeliveryQueue;
|
||||
protected _listeners?: LinkedList<Listener<T>>;
|
||||
|
||||
constructor(options?: EmitterOptions) {
|
||||
this._options = options;
|
||||
this._leakageMon = _globalLeakWarningThreshold > 0 ? new LeakageMonitor(this._options && this._options.leakWarningThreshold) : undefined;
|
||||
this._perfMon = this._options?._profName ? new EventProfiling(this._options._profName) : undefined;
|
||||
this._deliveryQueue = this._options?.deliveryQueue;
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
|
||||
// It is bad to have listeners at the time of disposing an emitter, it is worst to have listeners keep the emitter
|
||||
// alive via the reference that's embedded in their disposables. Therefore we loop over all remaining listeners and
|
||||
// unset their subscriptions/disposables. Looping and blaming remaining listeners is done on next tick because the
|
||||
// the following programming pattern is very popular:
|
||||
//
|
||||
// const someModel = this._disposables.add(new ModelObject()); // (1) create and register model
|
||||
// this._disposables.add(someModel.onDidChange(() => { ... }); // (2) subscribe and register model-event listener
|
||||
// ...later...
|
||||
// this._disposables.dispose(); disposes (1) then (2): don't warn after (1) but after the "overall dispose" is done
|
||||
|
||||
if (this._listeners) {
|
||||
if (_enableDisposeWithListenerWarning) {
|
||||
const listeners = Array.from(this._listeners);
|
||||
queueMicrotask(() => {
|
||||
for (const listener of listeners) {
|
||||
if (listener.subscription.isset()) {
|
||||
listener.subscription.unset();
|
||||
listener.stack?.print();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._listeners.clear();
|
||||
}
|
||||
this._deliveryQueue?.clear(this);
|
||||
this._options?.onLastListenerRemove?.();
|
||||
this._leakageMon?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -498,36 +655,46 @@ export class Emitter<T> {
|
||||
*/
|
||||
get event(): Event<T> {
|
||||
if (!this._event) {
|
||||
this._event = (listener: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => {
|
||||
this._event = (callback: (e: T) => any, thisArgs?: any, disposables?: IDisposable[] | DisposableStore) => {
|
||||
if (!this._listeners) {
|
||||
this._listeners = new LinkedList();
|
||||
}
|
||||
|
||||
const firstListener = this._listeners.isEmpty();
|
||||
|
||||
if (firstListener && this._options && this._options.onFirstListenerAdd) {
|
||||
if (firstListener && this._options?.onFirstListenerAdd) {
|
||||
this._options.onFirstListenerAdd(this);
|
||||
}
|
||||
|
||||
const remove = this._listeners.push(!thisArgs ? listener : [listener, thisArgs]);
|
||||
let removeMonitor: Function | undefined;
|
||||
let stack: Stacktrace | undefined;
|
||||
if (this._leakageMon && this._listeners.size >= 30) {
|
||||
// check and record this emitter for potential leakage
|
||||
stack = Stacktrace.create();
|
||||
removeMonitor = this._leakageMon.check(stack, this._listeners.size + 1);
|
||||
}
|
||||
|
||||
if (firstListener && this._options && this._options.onFirstListenerDidAdd) {
|
||||
if (_enableDisposeWithListenerWarning) {
|
||||
stack = stack ?? Stacktrace.create();
|
||||
}
|
||||
|
||||
const listener = new Listener(callback, thisArgs, stack);
|
||||
const removeListener = this._listeners.push(listener);
|
||||
|
||||
if (firstListener && this._options?.onFirstListenerDidAdd) {
|
||||
this._options.onFirstListenerDidAdd(this);
|
||||
}
|
||||
|
||||
if (this._options && this._options.onListenerDidAdd) {
|
||||
this._options.onListenerDidAdd(this, listener, thisArgs);
|
||||
if (this._options?.onListenerDidAdd) {
|
||||
this._options.onListenerDidAdd(this, callback, thisArgs);
|
||||
}
|
||||
|
||||
// check and record this emitter for potential leakage
|
||||
const removeMonitor = this._leakageMon?.check(this._listeners.size);
|
||||
|
||||
const result = toDisposable(() => {
|
||||
const result = listener.subscription.set(() => {
|
||||
if (removeMonitor) {
|
||||
removeMonitor();
|
||||
}
|
||||
if (!this._disposed) {
|
||||
remove();
|
||||
removeListener();
|
||||
if (this._options && this._options.onLastListenerRemove) {
|
||||
const hasListeners = (this._listeners && !this._listeners.isEmpty());
|
||||
if (!hasListeners) {
|
||||
@@ -560,54 +727,94 @@ export class Emitter<T> {
|
||||
// the driver of this
|
||||
|
||||
if (!this._deliveryQueue) {
|
||||
this._deliveryQueue = new LinkedList();
|
||||
this._deliveryQueue = new PrivateEventDeliveryQueue();
|
||||
}
|
||||
|
||||
for (let listener of this._listeners) {
|
||||
this._deliveryQueue.push([listener, event]);
|
||||
this._deliveryQueue.push(this, listener, event);
|
||||
}
|
||||
|
||||
// start/stop performance insight collection
|
||||
this._perfMon?.start(this._deliveryQueue.size);
|
||||
|
||||
while (this._deliveryQueue.size > 0) {
|
||||
const [listener, event] = this._deliveryQueue.shift()!;
|
||||
try {
|
||||
if (typeof listener === 'function') {
|
||||
listener.call(undefined, event);
|
||||
} else {
|
||||
listener[0].call(listener[1], event);
|
||||
}
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
}
|
||||
this._deliveryQueue.deliver();
|
||||
|
||||
this._perfMon?.stop();
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (!this._disposed) {
|
||||
this._disposed = true;
|
||||
this._listeners?.clear();
|
||||
this._deliveryQueue?.clear();
|
||||
this._options?.onLastListenerRemove?.();
|
||||
this._leakageMon?.dispose();
|
||||
hasListeners(): boolean {
|
||||
if (!this._listeners) {
|
||||
return false;
|
||||
}
|
||||
return (!this._listeners.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
export class EventDeliveryQueue {
|
||||
protected _queue = new LinkedList<EventDeliveryQueueElement>();
|
||||
|
||||
get size(): number {
|
||||
return this._queue.size;
|
||||
}
|
||||
|
||||
push<T>(emitter: Emitter<T>, listener: Listener<T>, event: T): void {
|
||||
this._queue.push(new EventDeliveryQueueElement(emitter, listener, event));
|
||||
}
|
||||
|
||||
clear<T>(emitter: Emitter<T>): void {
|
||||
const newQueue = new LinkedList<EventDeliveryQueueElement>();
|
||||
for (const element of this._queue) {
|
||||
if (element.emitter !== emitter) {
|
||||
newQueue.push(element);
|
||||
}
|
||||
}
|
||||
this._queue = newQueue;
|
||||
}
|
||||
|
||||
deliver(): void {
|
||||
while (this._queue.size > 0) {
|
||||
const element = this._queue.shift()!;
|
||||
try {
|
||||
element.listener.invoke(element.event);
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `EventDeliveryQueue` that is guaranteed to be used by a single `Emitter`.
|
||||
*/
|
||||
class PrivateEventDeliveryQueue extends EventDeliveryQueue {
|
||||
override clear<T>(emitter: Emitter<T>): void {
|
||||
// Here we can just clear the entire linked list because
|
||||
// all elements are guaranteed to belong to this emitter
|
||||
this._queue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class EventDeliveryQueueElement<T = any> {
|
||||
constructor(
|
||||
readonly emitter: Emitter<T>,
|
||||
readonly listener: Listener<T>,
|
||||
readonly event: T
|
||||
) { }
|
||||
}
|
||||
|
||||
export interface IWaitUntil {
|
||||
token: CancellationToken;
|
||||
waitUntil(thenable: Promise<unknown>): void;
|
||||
}
|
||||
|
||||
export type IWaitUntilData<T> = Omit<Omit<T, 'waitUntil'>, 'token'>;
|
||||
|
||||
export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
|
||||
|
||||
private _asyncDeliveryQueue?: LinkedList<[Listener<T>, Omit<T, 'waitUntil'>]>;
|
||||
private _asyncDeliveryQueue?: LinkedList<[Listener<T>, IWaitUntilData<T>]>;
|
||||
|
||||
async fireAsync(data: Omit<T, 'waitUntil'>, token: CancellationToken, promiseJoin?: (p: Promise<unknown>, listener: Function) => Promise<unknown>): Promise<void> {
|
||||
async fireAsync(data: IWaitUntilData<T>, token: CancellationToken, promiseJoin?: (p: Promise<unknown>, listener: Function) => Promise<unknown>): Promise<void> {
|
||||
if (!this._listeners) {
|
||||
return;
|
||||
}
|
||||
@@ -627,23 +834,20 @@ export class AsyncEmitter<T extends IWaitUntil> extends Emitter<T> {
|
||||
|
||||
const event = <T>{
|
||||
...data,
|
||||
token,
|
||||
waitUntil: (p: Promise<unknown>): void => {
|
||||
if (Object.isFrozen(thenables)) {
|
||||
throw new Error('waitUntil can NOT be called asynchronous');
|
||||
}
|
||||
if (promiseJoin) {
|
||||
p = promiseJoin(p, typeof listener === 'function' ? listener : listener[0]);
|
||||
p = promiseJoin(p, listener.callback);
|
||||
}
|
||||
thenables.push(p);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (typeof listener === 'function') {
|
||||
listener.call(undefined, event);
|
||||
} else {
|
||||
listener[0].call(listener[1], event);
|
||||
}
|
||||
listener.invoke(event);
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
continue;
|
||||
@@ -715,7 +919,7 @@ export class DebounceEmitter<T> extends PauseableEmitter<T> {
|
||||
private readonly _delay: number;
|
||||
private _handle: any | undefined;
|
||||
|
||||
constructor(options: EmitterOptions & { merge: (input: T[]) => T, delay?: number }) {
|
||||
constructor(options: EmitterOptions & { merge: (input: T[]) => T; delay?: number }) {
|
||||
super(options);
|
||||
this._delay = options.delay ?? 100;
|
||||
}
|
||||
@@ -763,7 +967,7 @@ export class EventMultiplexer<T> implements IDisposable {
|
||||
|
||||
private readonly emitter: Emitter<T>;
|
||||
private hasListeners = false;
|
||||
private events: { event: Event<T>; listener: IDisposable | null; }[] = [];
|
||||
private events: { event: Event<T>; listener: IDisposable | null }[] = [];
|
||||
|
||||
constructor() {
|
||||
this.emitter = new Emitter<T>({
|
||||
@@ -806,11 +1010,11 @@ export class EventMultiplexer<T> implements IDisposable {
|
||||
this.events.forEach(e => this.unhook(e));
|
||||
}
|
||||
|
||||
private hook(e: { event: Event<T>; listener: IDisposable | null; }): 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 | null; }): void {
|
||||
private unhook(e: { event: Event<T>; listener: IDisposable | null }): void {
|
||||
if (e.listener) {
|
||||
e.listener.dispose();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user