mirror of
https://github.com/cdr/code-server.git
synced 2025-12-11 10:54:11 +01:00
289 lines
7.2 KiB
TypeScript
289 lines
7.2 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { once } from 'vs/base/common/functional';
|
|
import { Iterable } from 'vs/base/common/iterator';
|
|
|
|
/**
|
|
* Enables logging of potentially leaked disposables.
|
|
*
|
|
* A disposable is considered leaked if it is not disposed or not registered as the child of
|
|
* another disposable. This tracking is very simple an only works for classes that either
|
|
* extend Disposable or use a DisposableStore. This means there are a lot of false positives.
|
|
*/
|
|
const TRACK_DISPOSABLES = false;
|
|
let disposableTracker: IDisposableTracker | null = null;
|
|
|
|
export interface IDisposableTracker {
|
|
trackDisposable(x: IDisposable): void;
|
|
markTracked(x: IDisposable): void;
|
|
}
|
|
|
|
export function setDisposableTracker(tracker: IDisposableTracker | null): void {
|
|
disposableTracker = tracker;
|
|
}
|
|
|
|
if (TRACK_DISPOSABLES) {
|
|
const __is_disposable_tracked__ = '__is_disposable_tracked__';
|
|
disposableTracker = new class implements IDisposableTracker {
|
|
trackDisposable(x: IDisposable): void {
|
|
const stack = new Error('Potentially leaked disposable').stack!;
|
|
setTimeout(() => {
|
|
if (!(x as any)[__is_disposable_tracked__]) {
|
|
console.log(stack);
|
|
}
|
|
}, 3000);
|
|
}
|
|
|
|
markTracked(x: IDisposable): void {
|
|
if (x && x !== Disposable.None) {
|
|
try {
|
|
(x as any)[__is_disposable_tracked__] = true;
|
|
} catch {
|
|
// noop
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
function markTracked<T extends IDisposable>(x: T): void {
|
|
if (!disposableTracker) {
|
|
return;
|
|
}
|
|
disposableTracker.markTracked(x);
|
|
}
|
|
|
|
export function trackDisposable<T extends IDisposable>(x: T): T {
|
|
if (!disposableTracker) {
|
|
return x;
|
|
}
|
|
disposableTracker.trackDisposable(x);
|
|
return x;
|
|
}
|
|
|
|
export class MultiDisposeError extends Error {
|
|
constructor(
|
|
public readonly errors: any[]
|
|
) {
|
|
super(`Encountered errors while disposing of store. Errors: [${errors.join(', ')}]`);
|
|
}
|
|
}
|
|
|
|
export interface IDisposable {
|
|
dispose(): void;
|
|
}
|
|
|
|
export function isDisposable<E extends object>(thing: E): thing is E & IDisposable {
|
|
return typeof (<IDisposable>thing).dispose === 'function' && (<IDisposable>thing).dispose.length === 0;
|
|
}
|
|
|
|
export function dispose<T extends IDisposable>(disposable: T): T;
|
|
export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;
|
|
export function dispose<T extends IDisposable, A extends IterableIterator<T> = IterableIterator<T>>(disposables: IterableIterator<T>): A;
|
|
export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
|
|
export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
|
|
export function dispose<T extends IDisposable>(arg: T | IterableIterator<T> | undefined): any {
|
|
if (Iterable.is(arg)) {
|
|
let errors: any[] = [];
|
|
|
|
for (const d of arg) {
|
|
if (d) {
|
|
markTracked(d);
|
|
try {
|
|
d.dispose();
|
|
} catch (e) {
|
|
errors.push(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (errors.length === 1) {
|
|
throw errors[0];
|
|
} else if (errors.length > 1) {
|
|
throw new MultiDisposeError(errors);
|
|
}
|
|
|
|
return Array.isArray(arg) ? [] : arg;
|
|
} else if (arg) {
|
|
markTracked(arg);
|
|
arg.dispose();
|
|
return arg;
|
|
}
|
|
}
|
|
|
|
|
|
export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
|
|
disposables.forEach(markTracked);
|
|
return toDisposable(() => dispose(disposables));
|
|
}
|
|
|
|
export function toDisposable(fn: () => void): IDisposable {
|
|
const self = trackDisposable({
|
|
dispose: () => {
|
|
markTracked(self);
|
|
fn();
|
|
}
|
|
});
|
|
return self;
|
|
}
|
|
|
|
export class DisposableStore implements IDisposable {
|
|
|
|
static DISABLE_DISPOSED_WARNING = false;
|
|
|
|
private _toDispose = new Set<IDisposable>();
|
|
private _isDisposed = false;
|
|
|
|
/**
|
|
* Dispose of all registered disposables and mark this object as disposed.
|
|
*
|
|
* Any future disposables added to this object will be disposed of on `add`.
|
|
*/
|
|
public dispose(): void {
|
|
if (this._isDisposed) {
|
|
return;
|
|
}
|
|
|
|
markTracked(this);
|
|
this._isDisposed = true;
|
|
this.clear();
|
|
}
|
|
|
|
/**
|
|
* Dispose of all registered disposables but do not mark this object as disposed.
|
|
*/
|
|
public clear(): void {
|
|
try {
|
|
dispose(this._toDispose.values());
|
|
} finally {
|
|
this._toDispose.clear();
|
|
}
|
|
}
|
|
|
|
public add<T extends IDisposable>(t: T): T {
|
|
if (!t) {
|
|
return t;
|
|
}
|
|
if ((t as unknown as DisposableStore) === this) {
|
|
throw new Error('Cannot register a disposable on itself!');
|
|
}
|
|
|
|
markTracked(t);
|
|
if (this._isDisposed) {
|
|
if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
|
|
console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack);
|
|
}
|
|
} else {
|
|
this._toDispose.add(t);
|
|
}
|
|
|
|
return t;
|
|
}
|
|
}
|
|
|
|
export abstract class Disposable implements IDisposable {
|
|
|
|
static readonly None = Object.freeze<IDisposable>({ dispose() { } });
|
|
|
|
private readonly _store = new DisposableStore();
|
|
|
|
constructor() {
|
|
trackDisposable(this);
|
|
}
|
|
|
|
public dispose(): void {
|
|
markTracked(this);
|
|
|
|
this._store.dispose();
|
|
}
|
|
|
|
protected _register<T extends IDisposable>(t: T): T {
|
|
if ((t as unknown as Disposable) === this) {
|
|
throw new Error('Cannot register a disposable on itself!');
|
|
}
|
|
return this._store.add(t);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Manages the lifecycle of a disposable value that may be changed.
|
|
*
|
|
* This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can
|
|
* also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up.
|
|
*/
|
|
export class MutableDisposable<T extends IDisposable> implements IDisposable {
|
|
private _value?: T;
|
|
private _isDisposed = false;
|
|
|
|
constructor() {
|
|
trackDisposable(this);
|
|
}
|
|
|
|
get value(): T | undefined {
|
|
return this._isDisposed ? undefined : this._value;
|
|
}
|
|
|
|
set value(value: T | undefined) {
|
|
if (this._isDisposed || value === this._value) {
|
|
return;
|
|
}
|
|
|
|
this._value?.dispose();
|
|
if (value) {
|
|
markTracked(value);
|
|
}
|
|
this._value = value;
|
|
}
|
|
|
|
clear() {
|
|
this.value = undefined;
|
|
}
|
|
|
|
dispose(): void {
|
|
this._isDisposed = true;
|
|
markTracked(this);
|
|
this._value?.dispose();
|
|
this._value = undefined;
|
|
}
|
|
}
|
|
|
|
export interface IReference<T> extends IDisposable {
|
|
readonly object: T;
|
|
}
|
|
|
|
export abstract class ReferenceCollection<T> {
|
|
|
|
private readonly references: Map<string, { readonly object: T; counter: number; }> = new Map();
|
|
|
|
acquire(key: string, ...args: any[]): IReference<T> {
|
|
let reference = this.references.get(key);
|
|
|
|
if (!reference) {
|
|
reference = { counter: 0, object: this.createReferencedObject(key, ...args) };
|
|
this.references.set(key, reference);
|
|
}
|
|
|
|
const { object } = reference;
|
|
const dispose = once(() => {
|
|
if (--reference!.counter === 0) {
|
|
this.destroyReferencedObject(key, reference!.object);
|
|
this.references.delete(key);
|
|
}
|
|
});
|
|
|
|
reference.counter++;
|
|
|
|
return { object, dispose };
|
|
}
|
|
|
|
protected abstract createReferencedObject(key: string, ...args: any[]): T;
|
|
protected abstract destroyReferencedObject(key: string, object: T): void;
|
|
}
|
|
|
|
export class ImmortalReference<T> implements IReference<T> {
|
|
constructor(public object: T) { }
|
|
dispose(): void { /* noop */ }
|
|
}
|