Path: blob/main/extensions/copilot/src/util/vs/base/common/lifecycle.ts
13405 views
//!!! DO NOT modify, this file was COPIED from 'microsoft/vscode'12/*---------------------------------------------------------------------------------------------3* Copyright (c) Microsoft Corporation. All rights reserved.4* Licensed under the MIT License. See License.txt in the project root for license information.5*--------------------------------------------------------------------------------------------*/67import { compareBy, numberComparator } from './arrays';8import { groupBy } from './collections';9import { SetMap, ResourceMap } from './map';10import { URI } from './uri';11import { createSingleCallFunction } from './functional';12import { Iterable } from './iterator';13import { BugIndicatingError, onUnexpectedError } from './errors';1415// #region Disposable Tracking1617/**18* Enables logging of potentially leaked disposables.19*20* A disposable is considered leaked if it is not disposed or not registered as the child of21* another disposable. This tracking is very simple an only works for classes that either22* extend Disposable or use a DisposableStore. This means there are a lot of false positives.23*/24const TRACK_DISPOSABLES = false;25let disposableTracker: IDisposableTracker | null = null;2627export interface IDisposableTracker {28/**29* Is called on construction of a disposable.30*/31trackDisposable(disposable: IDisposable): void;3233/**34* Is called when a disposable is registered as child of another disposable (e.g. {@link DisposableStore}).35* If parent is `null`, the disposable is removed from its former parent.36*/37setParent(child: IDisposable, parent: IDisposable | null): void;3839/**40* Is called after a disposable is disposed.41*/42markAsDisposed(disposable: IDisposable): void;4344/**45* Indicates that the given object is a singleton which does not need to be disposed.46*/47markAsSingleton(disposable: IDisposable): void;48}4950export class GCBasedDisposableTracker implements IDisposableTracker {5152private readonly _registry = new FinalizationRegistry<string>(heldValue => {53console.warn(`[LEAKED DISPOSABLE] ${heldValue}`);54});5556trackDisposable(disposable: IDisposable): void {57const stack = new Error('CREATED via:').stack!;58this._registry.register(disposable, stack, disposable);59}6061setParent(child: IDisposable, parent: IDisposable | null): void {62if (parent) {63this._registry.unregister(child);64} else {65this.trackDisposable(child);66}67}6869markAsDisposed(disposable: IDisposable): void {70this._registry.unregister(disposable);71}7273markAsSingleton(disposable: IDisposable): void {74this._registry.unregister(disposable);75}76}7778export interface DisposableInfo {79value: IDisposable;80source: string | null;81parent: IDisposable | null;82isSingleton: boolean;83idx: number;84}8586export class DisposableTracker implements IDisposableTracker {87private static idx = 0;8889private readonly livingDisposables = new Map<IDisposable, DisposableInfo>();9091private getDisposableData(d: IDisposable): DisposableInfo {92let val = this.livingDisposables.get(d);93if (!val) {94val = { parent: null, source: null, isSingleton: false, value: d, idx: DisposableTracker.idx++ };95this.livingDisposables.set(d, val);96}97return val;98}99100trackDisposable(d: IDisposable): void {101const data = this.getDisposableData(d);102if (!data.source) {103data.source =104new Error().stack!;105}106}107108setParent(child: IDisposable, parent: IDisposable | null): void {109const data = this.getDisposableData(child);110data.parent = parent;111}112113markAsDisposed(x: IDisposable): void {114this.livingDisposables.delete(x);115}116117markAsSingleton(disposable: IDisposable): void {118this.getDisposableData(disposable).isSingleton = true;119}120121private getRootParent(data: DisposableInfo, cache: Map<DisposableInfo, DisposableInfo>): DisposableInfo {122const cacheValue = cache.get(data);123if (cacheValue) {124return cacheValue;125}126127const result = data.parent ? this.getRootParent(this.getDisposableData(data.parent), cache) : data;128cache.set(data, result);129return result;130}131132getTrackedDisposables(): IDisposable[] {133const rootParentCache = new Map<DisposableInfo, DisposableInfo>();134135const leaking = [...this.livingDisposables.entries()]136.filter(([, v]) => v.source !== null && !this.getRootParent(v, rootParentCache).isSingleton)137.flatMap(([k]) => k);138139return leaking;140}141142computeLeakingDisposables(maxReported = 10, preComputedLeaks?: DisposableInfo[]): { leaks: DisposableInfo[]; details: string } | undefined {143let uncoveredLeakingObjs: DisposableInfo[] | undefined;144if (preComputedLeaks) {145uncoveredLeakingObjs = preComputedLeaks;146} else {147const rootParentCache = new Map<DisposableInfo, DisposableInfo>();148149const leakingObjects = [...this.livingDisposables.values()]150.filter((info) => info.source !== null && !this.getRootParent(info, rootParentCache).isSingleton);151152if (leakingObjects.length === 0) {153return;154}155const leakingObjsSet = new Set(leakingObjects.map(o => o.value));156157// Remove all objects that are a child of other leaking objects. Assumes there are no cycles.158uncoveredLeakingObjs = leakingObjects.filter(l => {159return !(l.parent && leakingObjsSet.has(l.parent));160});161162if (uncoveredLeakingObjs.length === 0) {163throw new Error('There are cyclic diposable chains!');164}165}166167if (!uncoveredLeakingObjs) {168return undefined;169}170171function getStackTracePath(leaking: DisposableInfo): string[] {172function removePrefix(array: string[], linesToRemove: (string | RegExp)[]) {173while (array.length > 0 && linesToRemove.some(regexp => typeof regexp === 'string' ? regexp === array[0] : array[0].match(regexp))) {174array.shift();175}176}177178const lines = leaking.source!.split('\n').map(p => p.trim().replace('at ', '')).filter(l => l !== '');179removePrefix(lines, ['Error', /^trackDisposable \(.*\)$/, /^DisposableTracker.trackDisposable \(.*\)$/]);180return lines.reverse();181}182183const stackTraceStarts = new SetMap<string, DisposableInfo>();184for (const leaking of uncoveredLeakingObjs) {185const stackTracePath = getStackTracePath(leaking);186for (let i = 0; i <= stackTracePath.length; i++) {187stackTraceStarts.add(stackTracePath.slice(0, i).join('\n'), leaking);188}189}190191// Put earlier leaks first192uncoveredLeakingObjs.sort(compareBy(l => l.idx, numberComparator));193194let message = '';195196let i = 0;197for (const leaking of uncoveredLeakingObjs.slice(0, maxReported)) {198i++;199const stackTracePath = getStackTracePath(leaking);200const stackTraceFormattedLines = [];201202for (let i = 0; i < stackTracePath.length; i++) {203let line = stackTracePath[i];204const starts = stackTraceStarts.get(stackTracePath.slice(0, i + 1).join('\n'));205line = `(shared with ${starts.size}/${uncoveredLeakingObjs.length} leaks) at ${line}`;206207const prevStarts = stackTraceStarts.get(stackTracePath.slice(0, i).join('\n'));208const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v);209delete continuations[stackTracePath[i]];210for (const [cont, set] of Object.entries(continuations)) {211if (set) {212stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`);213}214}215216stackTraceFormattedLines.unshift(line);217}218219message += `\n\n\n==================== Leaking disposable ${i}/${uncoveredLeakingObjs.length}: ${leaking.value.constructor.name} ====================\n${stackTraceFormattedLines.join('\n')}\n============================================================\n\n`;220}221222if (uncoveredLeakingObjs.length > maxReported) {223message += `\n\n\n... and ${uncoveredLeakingObjs.length - maxReported} more leaking disposables\n\n`;224}225226return { leaks: uncoveredLeakingObjs, details: message };227}228}229230export function setDisposableTracker(tracker: IDisposableTracker | null): void {231disposableTracker = tracker;232}233234if (TRACK_DISPOSABLES) {235const __is_disposable_tracked__ = '__is_disposable_tracked__';236setDisposableTracker(new class implements IDisposableTracker {237trackDisposable(x: IDisposable): void {238const stack = new Error('Potentially leaked disposable').stack!;239setTimeout(() => {240// eslint-disable-next-line local/code-no-any-casts241if (!(x as any)[__is_disposable_tracked__]) {242console.log(stack);243}244}, 3000);245}246247setParent(child: IDisposable, parent: IDisposable | null): void {248if (child && child !== Disposable.None) {249try {250// eslint-disable-next-line local/code-no-any-casts251(child as any)[__is_disposable_tracked__] = true;252} catch {253// noop254}255}256}257258markAsDisposed(disposable: IDisposable): void {259if (disposable && disposable !== Disposable.None) {260try {261// eslint-disable-next-line local/code-no-any-casts262(disposable as any)[__is_disposable_tracked__] = true;263} catch {264// noop265}266}267}268markAsSingleton(disposable: IDisposable): void { }269});270}271272export function trackDisposable<T extends IDisposable>(x: T): T {273disposableTracker?.trackDisposable(x);274return x;275}276277export function markAsDisposed(disposable: IDisposable): void {278disposableTracker?.markAsDisposed(disposable);279}280281function setParentOfDisposable(child: IDisposable, parent: IDisposable | null): void {282disposableTracker?.setParent(child, parent);283}284285function setParentOfDisposables(children: IDisposable[], parent: IDisposable | null): void {286if (!disposableTracker) {287return;288}289for (const child of children) {290disposableTracker.setParent(child, parent);291}292}293294/**295* Indicates that the given object is a singleton which does not need to be disposed.296*/297export function markAsSingleton<T extends IDisposable>(singleton: T): T {298disposableTracker?.markAsSingleton(singleton);299return singleton;300}301302// #endregion303304/**305* An object that performs a cleanup operation when `.dispose()` is called.306*307* Some examples of how disposables are used:308*309* - An event listener that removes itself when `.dispose()` is called.310* - A resource such as a file system watcher that cleans up the resource when `.dispose()` is called.311* - The return value from registering a provider. When `.dispose()` is called, the provider is unregistered.312*/313export interface IDisposable {314dispose(): void;315}316317/**318* Check if `thing` is {@link IDisposable disposable}.319*/320export function isDisposable<E>(thing: E): thing is E & IDisposable {321// eslint-disable-next-line local/code-no-any-casts322return typeof thing === 'object' && thing !== null && typeof (<IDisposable><any>thing).dispose === 'function' && (<IDisposable><any>thing).dispose.length === 0;323}324325/**326* Disposes of the value(s) passed in.327*/328export function dispose<T extends IDisposable>(disposable: T): T;329export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;330export function dispose<T extends IDisposable, A extends Iterable<T> = Iterable<T>>(disposables: A): A;331export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;332export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;333export function dispose<T extends IDisposable>(arg: T | Iterable<T> | undefined): any {334if (Iterable.is(arg)) {335const errors: any[] = [];336337for (const d of arg) {338if (d) {339try {340d.dispose();341} catch (e) {342errors.push(e);343}344}345}346347if (errors.length === 1) {348throw errors[0];349} else if (errors.length > 1) {350throw new AggregateError(errors, 'Encountered errors while disposing of store');351}352353return Array.isArray(arg) ? [] : arg;354} else if (arg) {355arg.dispose();356return arg;357}358}359360export function disposeIfDisposable<T extends IDisposable | object>(disposables: Array<T>): Array<T> {361for (const d of disposables) {362if (isDisposable(d)) {363d.dispose();364}365}366return [];367}368369/**370* Combine multiple disposable values into a single {@link IDisposable}.371*/372export function combinedDisposable(...disposables: IDisposable[]): IDisposable {373const parent = toDisposable(() => dispose(disposables));374setParentOfDisposables(disposables, parent);375return parent;376}377378class FunctionDisposable implements IDisposable {379private _isDisposed: boolean;380private readonly _fn: () => void;381382constructor(fn: () => void) {383this._isDisposed = false;384this._fn = fn;385trackDisposable(this);386}387388dispose() {389if (this._isDisposed) {390return;391}392if (!this._fn) {393throw new Error(`Unbound disposable context: Need to use an arrow function to preserve the value of this`);394}395this._isDisposed = true;396markAsDisposed(this);397this._fn();398}399}400401/**402* Turn a function that implements dispose into an {@link IDisposable}.403*404* @param fn Clean up function, guaranteed to be called only **once**.405*/406export function toDisposable(fn: () => void): IDisposable {407return new FunctionDisposable(fn);408}409410/**411* Manages a collection of disposable values.412*413* This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an414* `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a415* store that has already been disposed of.416*/417export class DisposableStore implements IDisposable {418419static DISABLE_DISPOSED_WARNING = false;420421private readonly _toDispose = new Set<IDisposable>();422private _isDisposed = false;423424constructor() {425trackDisposable(this);426}427428/**429* Dispose of all registered disposables and mark this object as disposed.430*431* Any future disposables added to this object will be disposed of on `add`.432*/433public dispose(): void {434if (this._isDisposed) {435return;436}437438markAsDisposed(this);439this._isDisposed = true;440this.clear();441}442443/**444* @return `true` if this object has been disposed of.445*/446public get isDisposed(): boolean {447return this._isDisposed;448}449450/**451* Dispose of all registered disposables but do not mark this object as disposed.452*/453public clear(): void {454if (this._toDispose.size === 0) {455return;456}457458try {459dispose(this._toDispose);460} finally {461this._toDispose.clear();462}463}464465/**466* Add a new {@link IDisposable disposable} to the collection.467*/468public add<T extends IDisposable>(o: T): T {469if (!o || o === Disposable.None) {470return o;471}472if ((o as unknown as DisposableStore) === this) {473throw new Error('Cannot register a disposable on itself!');474}475476setParentOfDisposable(o, this);477if (this._isDisposed) {478if (!DisposableStore.DISABLE_DISPOSED_WARNING) {479console.warn(new Error('Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!').stack);480}481} else {482this._toDispose.add(o);483}484485return o;486}487488/**489* Deletes a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the490* disposable even when the disposable is not part in the store.491*/492public delete<T extends IDisposable>(o: T): void {493if (!o) {494return;495}496if ((o as unknown as DisposableStore) === this) {497throw new Error('Cannot dispose a disposable on itself!');498}499this._toDispose.delete(o);500o.dispose();501}502503/**504* Deletes the value from the store, but does not dispose it.505*/506public deleteAndLeak<T extends IDisposable>(o: T): void {507if (!o) {508return;509}510if (this._toDispose.delete(o)) {511setParentOfDisposable(o, null);512}513}514515public assertNotDisposed(): void {516if (this._isDisposed) {517onUnexpectedError(new BugIndicatingError('Object disposed'));518}519}520}521522/**523* Abstract base class for a {@link IDisposable disposable} object.524*525* Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of.526*/527export abstract class Disposable implements IDisposable {528529/**530* A disposable that does nothing when it is disposed of.531*532* TODO: This should not be a static property.533*/534static readonly None = Object.freeze<IDisposable>({ dispose() { } });535536protected readonly _store = new DisposableStore();537538constructor() {539trackDisposable(this);540setParentOfDisposable(this._store, this);541}542543public dispose(): void {544markAsDisposed(this);545546this._store.dispose();547}548549/**550* Adds `o` to the collection of disposables managed by this object.551*/552protected _register<T extends IDisposable>(o: T): T {553if ((o as unknown as Disposable) === this) {554throw new Error('Cannot register a disposable on itself!');555}556return this._store.add(o);557}558}559560/**561* Manages the lifecycle of a disposable value that may be changed.562*563* This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can564* also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up.565*/566export class MutableDisposable<T extends IDisposable> implements IDisposable {567private _value?: T;568private _isDisposed = false;569570constructor() {571trackDisposable(this);572}573574/**575* Get the currently held disposable value, or `undefined` if this MutableDisposable has been disposed576*/577get value(): T | undefined {578return this._isDisposed ? undefined : this._value;579}580581/**582* Set a new disposable value.583*584* Behaviour:585* - If the MutableDisposable has been disposed, the setter is a no-op.586* - If the new value is strictly equal to the current value, the setter is a no-op.587* - Otherwise the previous value (if any) is disposed and the new value is stored.588*589* Related helpers:590* - clear() resets the value to `undefined` (and disposes the previous value).591* - clearAndLeak() returns the old value without disposing it and removes its parent.592*/593set value(value: T | undefined) {594if (this._isDisposed || value === this._value) {595return;596}597598this._value?.dispose();599if (value) {600setParentOfDisposable(value, this);601}602this._value = value;603}604605/**606* Resets the stored value and disposed of the previously stored value.607*/608clear(): void {609this.value = undefined;610}611612dispose(): void {613this._isDisposed = true;614markAsDisposed(this);615this._value?.dispose();616this._value = undefined;617}618619/**620* Clears the value, but does not dispose it.621* The old value is returned.622*/623clearAndLeak(): T | undefined {624const oldValue = this._value;625this._value = undefined;626if (oldValue) {627setParentOfDisposable(oldValue, null);628}629return oldValue;630}631}632633/**634* Manages the lifecycle of a disposable value that may be changed like {@link MutableDisposable}, but the value must635* exist and cannot be undefined.636*/637export class MandatoryMutableDisposable<T extends IDisposable> implements IDisposable {638private readonly _disposable = new MutableDisposable<T>();639private _isDisposed = false;640641constructor(initialValue: T) {642this._disposable.value = initialValue;643}644645get value(): T {646return this._disposable.value!;647}648649set value(value: T) {650if (this._isDisposed || value === this._disposable.value) {651return;652}653this._disposable.value = value;654}655656dispose() {657this._isDisposed = true;658this._disposable.dispose();659}660}661662export class RefCountedDisposable {663664private _counter: number = 1;665666constructor(667private readonly _disposable: IDisposable,668) { }669670acquire() {671this._counter++;672return this;673}674675release() {676if (--this._counter === 0) {677this._disposable.dispose();678}679return this;680}681}682683export interface IReference<T> extends IDisposable {684readonly object: T;685}686687export abstract class ReferenceCollection<T> {688689private readonly references: Map<string, { readonly object: T; counter: number }> = new Map();690691acquire(key: string, ...args: unknown[]): IReference<T> {692let reference = this.references.get(key);693694if (!reference) {695reference = { counter: 0, object: this.createReferencedObject(key, ...args) };696this.references.set(key, reference);697}698699const { object } = reference;700const dispose = createSingleCallFunction(() => {701if (--reference.counter === 0) {702this.destroyReferencedObject(key, reference.object);703this.references.delete(key);704}705});706707reference.counter++;708709return { object, dispose };710}711712protected abstract createReferencedObject(key: string, ...args: unknown[]): T;713protected abstract destroyReferencedObject(key: string, object: T): void;714}715716/**717* Unwraps a reference collection of promised values. Makes sure718* references are disposed whenever promises get rejected.719*/720export class AsyncReferenceCollection<T> {721722constructor(private referenceCollection: ReferenceCollection<Promise<T>>) { }723724async acquire(key: string, ...args: any[]): Promise<IReference<T>> {725const ref = this.referenceCollection.acquire(key, ...args);726727try {728const object = await ref.object;729730return {731object,732dispose: () => ref.dispose()733};734} catch (error) {735ref.dispose();736throw error;737}738}739}740741export class ImmortalReference<T> implements IReference<T> {742constructor(public object: T) { }743dispose(): void { /* noop */ }744}745746export function disposeOnReturn(fn: (store: DisposableStore) => void): void {747const store = new DisposableStore();748try {749fn(store);750} finally {751store.dispose();752}753}754755/**756* A map the manages the lifecycle of the values that it stores.757*/758export class DisposableMap<K, V extends IDisposable = IDisposable> implements IDisposable {759760private readonly _store: Map<K, V>;761private _isDisposed = false;762763constructor(store: Map<K, V> = new Map<K, V>()) {764this._store = store;765trackDisposable(this);766}767768/**769* Disposes of all stored values and mark this object as disposed.770*771* Trying to use this object after it has been disposed of is an error.772*/773dispose(): void {774markAsDisposed(this);775this._isDisposed = true;776this.clearAndDisposeAll();777}778779/**780* Disposes of all stored values and clear the map, but DO NOT mark this object as disposed.781*/782clearAndDisposeAll(): void {783if (!this._store.size) {784return;785}786787try {788dispose(this._store.values());789} finally {790this._store.clear();791}792}793794has(key: K): boolean {795return this._store.has(key);796}797798get size(): number {799return this._store.size;800}801802get(key: K): V | undefined {803return this._store.get(key);804}805806set(key: K, value: V, skipDisposeOnOverwrite = false): void {807if (this._isDisposed) {808console.warn(new Error('Trying to add a disposable to a DisposableMap that has already been disposed of. The added object will be leaked!').stack);809}810811if (!skipDisposeOnOverwrite) {812this._store.get(key)?.dispose();813}814815this._store.set(key, value);816setParentOfDisposable(value, this);817}818819/**820* Delete the value stored for `key` from this map and also dispose of it.821*/822deleteAndDispose(key: K): void {823this._store.get(key)?.dispose();824this._store.delete(key);825}826827/**828* Delete the value stored for `key` from this map but return it. The caller is829* responsible for disposing of the value.830*/831deleteAndLeak(key: K): V | undefined {832const value = this._store.get(key);833if (value) {834setParentOfDisposable(value, null);835}836this._store.delete(key);837return value;838}839840keys(): IterableIterator<K> {841return this._store.keys();842}843844values(): IterableIterator<V> {845return this._store.values();846}847848[Symbol.iterator](): IterableIterator<[K, V]> {849return this._store[Symbol.iterator]();850}851}852853/**854* Call `then` on a Promise, unless the returned disposable is disposed.855*/856export function thenIfNotDisposed<T>(promise: Promise<T>, then: (result: T) => void): IDisposable {857let disposed = false;858promise.then(result => {859if (disposed) {860return;861}862then(result);863});864return toDisposable(() => {865disposed = true;866});867}868869/**870* Call `then` on a promise that resolves to a {@link IDisposable}, then either register the871* disposable or register it to the {@link DisposableStore}, depending on whether the store is872* disposed or not.873*/874export function thenRegisterOrDispose<T extends IDisposable>(promise: Promise<T>, store: DisposableStore): Promise<T> {875return promise.then(disposable => {876if (store.isDisposed) {877disposable.dispose();878} else {879store.add(disposable);880}881return disposable;882});883}884885export class DisposableResourceMap<V extends IDisposable = IDisposable> extends DisposableMap<URI, V> {886constructor() {887super(new ResourceMap());888}889}890891892