src/vs/base/common/lifecycle.ts

974 LOC · 828 covered · 146 uncovered · 209 ranges · 20961 concepts · 81 introducers · 12741 tests

File neighbourhood

The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.

Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file

In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.

Graph controls are ready.

Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.

1 > /*--------------------------------------------------------------------------------------------- map.ts ×97
2 > * Copyright (c) Microsoft Corporation. All rights reserved.
3 > * Licensed under the MIT License. See License.txt in the project root for license information.
4 > *--------------------------------------------------------------------------------------------*/
5 >
6 > import { compareBy, numberComparator } from './arrays.js';
7 > import { groupBy } from './collections.js';
8 > import { SetMap, ResourceMap } from './map.js';
9 > import { URI } from './uri.js';
10 > import { createSingleCallFunction } from './functional.js';
11 > import { Iterable } from './iterator.js';
12 > import { BugIndicatingError, onUnexpectedError } from './errors.js';
13 >
14 > // #region Disposable Tracking
15 >
16 > /**
17 > * Enables logging of potentially leaked disposables.
18 > *
19 > * A disposable is considered leaked if it is not disposed or not registered as the child of
20 > * another disposable. This tracking is very simple an only works for classes that either
21 > * extend Disposable or use a DisposableStore. This means there are a lot of false positives.
22 > */
23 > const TRACK_DISPOSABLES = false;
24 > let disposableTracker: IDisposableTracker | null = null;
25 >
26 > export interface IDisposableTracker {
27 > /**
28 > * Is called on construction of a disposable.
29 > */
30 > trackDisposable(disposable: IDisposable): void;
31 >
32 > /**
33 > * Is called when a disposable is registered as child of another disposable (e.g. {@link DisposableStore}).
34 > * If parent is `null`, the disposable is removed from its former parent.
35 > */
36 > setParent(child: IDisposable, parent: IDisposable | null): void;
37 >
38 > /**
39 > * Is called after a disposable is disposed.
40 > */
41 > markAsDisposed(disposable: IDisposable): void;
42 >
43 > /**
44 > * Indicates that the given object is a singleton which does not need to be disposed.
45 > */
46 > markAsSingleton(disposable: IDisposable): void;
47 > }
48 >
49 > export class GCBasedDisposableTracker implements IDisposableTracker {
50
51 private readonly _registry = new FinalizationRegistry<string>(heldValue => {
52 console.warn(`[LEAKED DISPOSABLE] ${heldValue}`);
53 });
55 > trackDisposable(disposable: IDisposable): void {
56 const stack = new Error('CREATED via:').stack!;
57 this._registry.register(disposable, stack, disposable);
58 }
60 > setParent(child: IDisposable, parent: IDisposable | null): void {
61 if (parent) {
62 this._registry.unregister(child);
63 } else {
64 this.trackDisposable(child);
65 }
66 }
68 > markAsDisposed(disposable: IDisposable): void {
69 this._registry.unregister(disposable);
70 }
72 > markAsSingleton(disposable: IDisposable): void {
73 this._registry.unregister(disposable);
74 }
75 > } map.ts ×97
76 >
77 > export interface DisposableInfo {
78 > value: IDisposable;
79 > source: string | null;
80 > parent: IDisposable | null;
81 > isSingleton: boolean;
82 > idx: number;
83 > }
84 >
85 > export class DisposableTracker implements IDisposableTracker {
86 > private static idx = 0; lifecycle.ts ×6
87 >
88 > private readonly livingDisposables = new Map<IDisposable, DisposableInfo>();
90 > private getDisposableData(d: IDisposable): DisposableInfo {
91 > let val = this.livingDisposables.get(d); lifecycle.ts ×1
92 > if (!val) {
93 > val = { parent: null, source: null, isSingleton: false, value: d, idx: DisposableTracker.idx++ };
94 > this.livingDisposables.set(d, val);
95 > }
96 > return val;
97 > }
99 > trackDisposable(d: IDisposable): void {
100 > const data = this.getDisposableData(d); lifecycle.ts ×1
101 > if (!data.source) {
102 > data.source =
103 > new Error().stack!;
104 > }
105 > }
106 > map.ts ×97
107 > setParent(child: IDisposable, parent: IDisposable | null): void {
108 > const data = this.getDisposableData(child); lifecycle.ts ×1
109 > data.parent = parent;
110 > }
111 > map.ts ×97
112 > markAsDisposed(x: IDisposable): void {
113 > this.livingDisposables.delete(x); lifecycle.ts ×2
114 > }
115 > map.ts ×97
116 > markAsSingleton(disposable: IDisposable): void {
117 > this.getDisposableData(disposable).isSingleton = true; lifecycle.ts ×1
118 > }
119 > map.ts ×97
120 > private getRootParent(data: DisposableInfo, cache: Map<DisposableInfo, DisposableInfo>): DisposableInfo {
121 > const cacheValue = cache.get(data); lifecycle.ts ×2
122 > if (cacheValue) {
123 > return cacheValue; lifecycle.ts ×1
124 > }
126 > const result = data.parent ? this.getRootParent(this.getDisposableData(data.parent), cache) : data;
127 > cache.set(data, result);
128 > return result;
129 > }
130 > map.ts ×97
131 > getTrackedDisposables(): IDisposable[] {
132 > const rootParentCache = new Map<DisposableInfo, DisposableInfo>(); lifecycle.ts ×1
133 >
134 > const leaking = [...this.livingDisposables.entries()]
135 > .filter(([, v]) => v.source !== null && !this.getRootParent(v, rootParentCache).isSingleton)
136 > .flatMap(([k]) => k);
137 >
138 > return leaking;
139 > }
140 > map.ts ×97
141 > computeLeakingDisposables(maxReported = 10, preComputedLeaks?: DisposableInfo[]): { leaks: DisposableInfo[]; details: string } | undefined {
142 > let uncoveredLeakingObjs: DisposableInfo[] | undefined; lifecycle.ts ×6
143 > if (preComputedLeaks) {
144 uncoveredLeakingObjs = preComputedLeaks;
145 > } else { lifecycle.ts ×6
146 > const rootParentCache = new Map<DisposableInfo, DisposableInfo>();
147 >
148 > const leakingObjects = [...this.livingDisposables.values()]
149 > .filter((info) => info.source !== null && !this.getRootParent(info, rootParentCache).isSingleton);
150 >
151 > if (leakingObjects.length === 0) {
152 > return; lifecycle.ts ×2
153 > }
154 > const leakingObjsSet = new Set(leakingObjects.map(o => o.value)); lifecycle.ts ×5
155 >
156 > // Remove all objects that are a child of other leaking objects. Assumes there are no cycles.
157 > uncoveredLeakingObjs = leakingObjects.filter(l => {
158 > return !(l.parent && leakingObjsSet.has(l.parent));
159 > });
160 >
161 > if (uncoveredLeakingObjs.length === 0) {
162 throw new Error('There are cyclic diposable chains!');
163 }
166 > if (!uncoveredLeakingObjs) {
167 return undefined;
168 }
170 > function getStackTracePath(leaking: DisposableInfo): string[] {
171 > function removePrefix(array: string[], linesToRemove: (string | RegExp)[]) {
172 > while (array.length > 0 && linesToRemove.some(regexp => typeof regexp === 'string' ? regexp === array[0] : array[0].match(regexp))) {
173 > array.shift();
174 > }
175 > }
176 >
177 > const lines = leaking.source!.split('\n').map(p => p.trim().replace('at ', '')).filter(l => l !== '');
178 > removePrefix(lines, ['Error', /^trackDisposable \(.*\)$/, /^DisposableTracker.trackDisposable \(.*\)$/]);
179 > return lines.reverse();
180 > }
181 >
182 > const stackTraceStarts = new SetMap<string, DisposableInfo>();
183 > for (const leaking of uncoveredLeakingObjs) {
184 > const stackTracePath = getStackTracePath(leaking);
185 > for (let i = 0; i <= stackTracePath.length; i++) {
186 > stackTraceStarts.add(stackTracePath.slice(0, i).join('\n'), leaking);
187 > }
188 > }
189 >
190 > // Put earlier leaks first
191 > uncoveredLeakingObjs.sort(compareBy(l => l.idx, numberComparator));
192 >
193 > let message = '';
194 >
195 > let i = 0;
196 > for (const leaking of uncoveredLeakingObjs.slice(0, maxReported)) {
197 > i++;
198 > const stackTracePath = getStackTracePath(leaking);
199 > const stackTraceFormattedLines = [];
200 >
201 > for (let i = 0; i < stackTracePath.length; i++) {
202 > let line = stackTracePath[i];
203 > const starts = stackTraceStarts.get(stackTracePath.slice(0, i + 1).join('\n'));
204 > line = `(shared with ${starts.size}/${uncoveredLeakingObjs.length} leaks) at ${line}`;
205 >
206 > const prevStarts = stackTraceStarts.get(stackTracePath.slice(0, i).join('\n'));
207 > const continuations = groupBy([...prevStarts].map(d => getStackTracePath(d)[i]), v => v);
208 > delete continuations[stackTracePath[i]];
209 > for (const [cont, set] of Object.entries(continuations)) {
210 if (set) {
211 stackTraceFormattedLines.unshift(` - stacktraces of ${set.length} other leaks continue with ${cont}`);
212 }
213 }
215 > stackTraceFormattedLines.unshift(line);
216 > }
217 >
218 > message += `\n\n\n==================== Leaking disposable ${i}/${uncoveredLeakingObjs.length}: ${leaking.value.constructor.name} ====================\n${stackTraceFormattedLines.join('\n')}\n============================================================\n\n`;
219 > }
220 >
221 > if (uncoveredLeakingObjs.length > maxReported) {
222 message += `\n\n\n... and ${uncoveredLeakingObjs.length - maxReported} more leaking disposables\n\n`;
223 }
225 > return { leaks: uncoveredLeakingObjs, details: message };
227 > } map.ts ×97
228 >
229 > export function setDisposableTracker(tracker: IDisposableTracker | null): void {
230 > disposableTracker = tracker; lifecycle.ts ×6
231 > }
232 > map.ts ×97
233 > if (TRACK_DISPOSABLES) {
234 const __is_disposable_tracked__ = '__is_disposable_tracked__';
235 setDisposableTracker(new class implements IDisposableTracker {
236 trackDisposable(x: IDisposable): void {
237 const stack = new Error('Potentially leaked disposable').stack!;
238 setTimeout(() => {
239 // eslint-disable-next-line local/code-no-any-casts
240 if (!(x as any)[__is_disposable_tracked__]) {
241 console.log(stack);
242 }
243 }, 3000);
244 }
245
246 setParent(child: IDisposable, parent: IDisposable | null): void {
247 if (child && child !== Disposable.None) {
248 try {
249 // eslint-disable-next-line local/code-no-any-casts
250 (child as any)[__is_disposable_tracked__] = true;
251 } catch {
252 // noop
253 }
254 }
255 }
256
257 markAsDisposed(disposable: IDisposable): void {
258 if (disposable && disposable !== Disposable.None) {
259 try {
260 // eslint-disable-next-line local/code-no-any-casts
261 (disposable as any)[__is_disposable_tracked__] = true;
262 } catch {
263 // noop
264 }
265 }
266 }
267 markAsSingleton(disposable: IDisposable): void { }
268 });
269 }
270 > map.ts ×97
271 > export function trackDisposable<T extends IDisposable>(x: T): T {
272 > disposableTracker?.trackDisposable(x); lifecycle.ts ×1
273 > return x;
274 > }
275 > map.ts ×97
276 > export function markAsDisposed(disposable: IDisposable): void {
277 > disposableTracker?.markAsDisposed(disposable); lifecycle.ts ×1
278 > }
279 > map.ts ×97
280 > function setParentOfDisposable(child: IDisposable, parent: IDisposable | null): void { lifecycle.ts ×1
281 > disposableTracker?.setParent(child, parent);
282 > }
283 > map.ts ×97
284 > function setParentOfDisposables(children: IDisposable[], parent: IDisposable | null): void { lifecycle.ts ×2
285 > if (!disposableTracker) {
286 > return; lifecycle.ts ×1
287 > }
288 > for (const child of children) { lifecycle.ts ×1
289 > disposableTracker.setParent(child, parent);
290 > }
291 > }
292 > map.ts ×97
293 > /**
294 > * Indicates that the given object is a singleton which does not need to be disposed.
295 > */
296 > export function markAsSingleton<T extends IDisposable>(singleton: T): T {
297 > disposableTracker?.markAsSingleton(singleton); lifecycle.ts ×1
298 > return singleton;
299 > }
300 > map.ts ×97
301 > // #endregion
302 >
303 > /**
304 > * An object that performs a cleanup operation when `.dispose()` is called.
305 > *
306 > * Some examples of how disposables are used:
307 > *
308 > * - An event listener that removes itself when `.dispose()` is called.
309 > * - A resource such as a file system watcher that cleans up the resource when `.dispose()` is called.
310 > * - The return value from registering a provider. When `.dispose()` is called, the provider is unregistered.
311 > */
312 > export interface IDisposable {
313 > dispose(): void;
314 > }
315 >
316 > /**
317 > * Check if `thing` is {@link IDisposable disposable}.
318 > */
319 > export function isDisposable<E>(thing: E): thing is E & IDisposable {
320 > // eslint-disable-next-line local/code-no-any-casts lifecycle.ts ×1
321 > return typeof thing === 'object' && thing !== null && typeof (<IDisposable><any>thing).dispose === 'function' && (<IDisposable><any>thing).dispose.length === 0;
322 > }
323 > map.ts ×97
324 > /**
325 > * Disposes of the value(s) passed in.
326 > */
327 > export function dispose<T extends IDisposable>(disposable: T): T;
328 > export function dispose<T extends IDisposable>(disposable: T | undefined): T | undefined;
329 > export function dispose<T extends IDisposable, A extends Iterable<T> = Iterable<T>>(disposables: A): A;
330 > export function dispose<T extends IDisposable>(disposables: Array<T>): Array<T>;
331 > export function dispose<T extends IDisposable>(disposables: ReadonlyArray<T>): ReadonlyArray<T>;
332 > export function dispose<T extends IDisposable>(arg: T | Iterable<T> | undefined): any {
333 > if (Iterable.is(arg)) { lifecycle.ts ×3
334 > const errors: any[] = []; lifecycle.ts ×4
335 >
336 > for (const d of arg) {
337 > if (d) { lifecycle.ts ×2
338 > try {
339 > d.dispose();
340 > } catch (e) {
341 > errors.push(e); lifecycle.ts ×1
342 > }
344 > }
346 > if (errors.length === 1) {
347 > throw errors[0]; lifecycle.ts ×1
348 > } else if (errors.length > 1) { lifecycle.ts ×4
349 > throw new AggregateError(errors, 'Encountered errors while disposing of store'); lifecycle.ts ×1
350 > }
352 > return Array.isArray(arg) ? [] : arg; lifecycle.ts ×4
353 > } else if (arg) { lifecycle.ts ×3
354 > arg.dispose(); lifecycle.ts ×1
355 > return arg;
356 > }
358 > map.ts ×97
359 > export function disposeIfDisposable<T extends IDisposable | object>(disposables: Array<T>): Array<T> {
360 for (const d of disposables) {
361 if (isDisposable(d)) {
362 d.dispose();
363 }
364 }
365 return [];
366 }
367 > map.ts ×97
368 > /**
369 > * Combine multiple disposable values into a single {@link IDisposable}.
370 > */
371 > export function combinedDisposable(...disposables: IDisposable[]): IDisposable {
372 > const parent = toDisposable(() => dispose(disposables)); lifecycle.ts ×2
373 > setParentOfDisposables(disposables, parent);
374 > return parent;
375 > }
376 > map.ts ×97
377 > class FunctionDisposable implements IDisposable {
378 > private _isDisposed: boolean;
379 > private readonly _fn: () => void;
380 >
381 > constructor(fn: () => void) {
382 > this._isDisposed = false; lifecycle.ts ×2
383 > this._fn = fn;
384 > trackDisposable(this);
385 > }
386 > map.ts ×97
387 > dispose() {
388 > if (this._isDisposed) { lifecycle.ts ×3
389 > return; lifecycle.ts ×1
390 > }
391 > if (!this._fn) { lifecycle.ts ×3
392 throw new Error(`Unbound disposable context: Need to use an arrow function to preserve the value of this`);
393 }
394 > this._isDisposed = true; lifecycle.ts ×3
395 > markAsDisposed(this);
396 > this._fn();
397 > }
398 > } map.ts ×97
399 >
400 > /**
401 > * Turn a function that implements dispose into an {@link IDisposable}.
402 > *
403 > * @param fn Clean up function, guaranteed to be called only **once**.
404 > */
405 > export function toDisposable(fn: () => void): IDisposable {
406 > return new FunctionDisposable(fn); lifecycle.ts ×2
407 > }
408 > map.ts ×97
409 > /**
410 > * Manages a collection of disposable values.
411 > *
412 > * This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an
413 > * `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a
414 > * store that has already been disposed of.
415 > */
416 > export class DisposableStore implements IDisposable {
417 >
418 > static DISABLE_DISPOSED_WARNING = false;
419 >
420 > private readonly _toDispose = new Set<IDisposable>();
421 > private _isDisposed = false;
422 >
423 > constructor() {
424 > trackDisposable(this); lifecycle.ts ×1
425 > }
426 > map.ts ×97
427 > /**
428 > * Dispose of all registered disposables and mark this object as disposed.
429 > *
430 > * Any future disposables added to this object will be disposed of on `add`.
431 > */
432 > public dispose(): void {
433 > if (this._isDisposed) { lifecycle.ts ×4
434 > return; lifecycle.ts ×1
435 > }
437 > markAsDisposed(this);
438 > this._isDisposed = true;
439 > this.clear();
440 > }
441 > map.ts ×97
442 > /**
443 > * @return `true` if this object has been disposed of.
444 > */
445 > public get isDisposed(): boolean {
446 > return this._isDisposed; lifecycle.ts ×1
447 > }
448 > map.ts ×97
449 > /**
450 > * Dispose of all registered disposables but do not mark this object as disposed.
451 > */
452 > public clear(): void {
453 > if (this._toDispose.size === 0) { lifecycle.ts ×4
454 > return; lifecycle.ts ×1
455 > }
457 > try {
458 > dispose(this._toDispose);
459 > } finally {
460 > this._toDispose.clear();
461 > }
463 > map.ts ×97
464 > /**
465 > * Add a new {@link IDisposable disposable} to the collection.
466 > */
467 > public add<T extends IDisposable>(o: T): T {
468 > if (!o || o === Disposable.None) { lifecycle.ts ×2
469 > return o; lifecycle.ts ×1
470 > }
471 > if ((o as unknown as DisposableStore) === this) { lifecycle.ts ×3
472 throw new Error('Cannot register a disposable on itself!');
473 }
475 > setParentOfDisposable(o, this);
476 > if (this._isDisposed) {
477 if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
478 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);
479 }
480 > } else { lifecycle.ts ×3
481 > this._toDispose.add(o);
482 > }
483 >
484 > return o;
486 > map.ts ×97
487 > /**
488 > * Deletes a disposable from store and disposes of it. This will not throw or warn and proceed to dispose the
489 > * disposable even when the disposable is not part in the store.
490 > */
491 > public delete<T extends IDisposable>(o: T): void {
492 > if (!o) { lifecycle.ts ×3
493 return;
494 }
495 > if ((o as unknown as DisposableStore) === this) { lifecycle.ts ×3
496 throw new Error('Cannot dispose a disposable on itself!');
497 }
498 > this._toDispose.delete(o); lifecycle.ts ×3
499 > o.dispose();
500 > }
501 > map.ts ×97
502 > /**
503 > * Deletes the value from the store, but does not dispose it.
504 > */
505 > public deleteAndLeak<T extends IDisposable>(o: T): void {
506 > if (!o) { lifecycle.ts ×2
507 return;
508 }
509 > if (this._toDispose.delete(o)) { lifecycle.ts ×2
510 > setParentOfDisposable(o, null);
511 > }
512 > }
513 > map.ts ×97
514 > public assertNotDisposed(): void {
515 if (this._isDisposed) {
516 onUnexpectedError(new BugIndicatingError('Object disposed'));
517 }
518 }
519 > } map.ts ×97
520 >
521 > /**
522 > * Abstract base class for a {@link IDisposable disposable} object.
523 > *
524 > * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of.
525 > */
526 > export abstract class Disposable implements IDisposable {
527 >
528 > /**
529 > * A disposable that does nothing when it is disposed of.
530 > *
531 > * TODO: This should not be a static property.
532 > */
533 > static readonly None = Object.freeze<IDisposable>({ dispose() { } });
534 >
535 > protected readonly _store = new DisposableStore();
536 >
537 > constructor() {
538 > trackDisposable(this); lifecycle.ts ×1
539 > setParentOfDisposable(this._store, this);
540 > }
541 > map.ts ×97
542 > public dispose(): void {
543 > markAsDisposed(this); lifecycle.ts ×1
544 >
545 > this._store.dispose();
546 > }
547 > map.ts ×97
548 > /**
549 > * Adds `o` to the collection of disposables managed by this object.
550 > */
551 > protected _register<T extends IDisposable>(o: T): T {
552 > if ((o as unknown as Disposable) === this) { lifecycle.ts ×2
553 throw new Error('Cannot register a disposable on itself!');
554 }
555 > return this._store.add(o); lifecycle.ts ×2
556 > }
557 > } map.ts ×97
558 >
559 > /**
560 > * Manages the lifecycle of a disposable value that may be changed.
561 > *
562 > * This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can
563 > * also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up.
564 > */
565 > export class MutableDisposable<T extends IDisposable> implements IDisposable {
566 > private _value?: T;
567 > private _isDisposed = false;
568 >
569 > constructor() {
570 > trackDisposable(this); lifecycle.ts ×1
571 > }
572 > map.ts ×97
573 > /**
574 > * Get the currently held disposable value, or `undefined` if this MutableDisposable has been disposed
575 > */
576 > get value(): T | undefined {
577 > return this._isDisposed ? undefined : this._value; lifecycle.ts ×1
578 > }
579 > map.ts ×97
580 > /**
581 > * Set a new disposable value.
582 > *
583 > * Behaviour:
584 > * - If the MutableDisposable has been disposed, the setter is a no-op.
585 > * - If the new value is strictly equal to the current value, the setter is a no-op.
586 > * - Otherwise the previous value (if any) is disposed and the new value is stored.
587 > *
588 > * Related helpers:
589 > * - clear() resets the value to `undefined` (and disposes the previous value).
590 > * - clearAndLeak() returns the old value without disposing it and removes its parent.
591 > */
592 > set value(value: T | undefined) {
593 > if (this._isDisposed || value === this._value) { lifecycle.ts ×3
594 > return; lifecycle.ts ×1
595 > }
597 > this._value?.dispose(); lifecycle.ts ×3
598 > if (value) {
599 > setParentOfDisposable(value, this); lifecycle.ts ×2
600 > }
601 > this._value = value;
603 > map.ts ×97
604 > /**
605 > * Resets the stored value and disposed of the previously stored value.
606 > */
607 > clear(): void {
608 > this.value = undefined; lifecycle.ts ×1
609 > }
610 > map.ts ×97
611 > dispose(): void {
612 > this._isDisposed = true; lifecycle.ts ×1
613 > markAsDisposed(this);
614 > this._value?.dispose();
615 > this._value = undefined;
616 > }
617 > map.ts ×97
618 > /**
619 > * Clears the value, but does not dispose it.
620 > * The old value is returned.
621 > */
622 > clearAndLeak(): T | undefined {
623 const oldValue = this._value;
624 this._value = undefined;
625 if (oldValue) {
626 setParentOfDisposable(oldValue, null);
627 }
628 return oldValue;
629 }
630 > } map.ts ×97
631 >
632 > /**
633 > * Manages the lifecycle of a disposable value that may be changed like {@link MutableDisposable}, but the value must
634 > * exist and cannot be undefined.
635 > */
636 > export class MandatoryMutableDisposable<T extends IDisposable> implements IDisposable {
637 > private readonly _disposable = new MutableDisposable<T>();
638 > private _isDisposed = false;
639 >
640 > constructor(initialValue: T) {
641 this._disposable.value = initialValue;
642 }
643 > map.ts ×97
644 > get value(): T {
645 return this._disposable.value!;
646 }
647 > map.ts ×97
648 > set value(value: T) {
649 if (this._isDisposed || value === this._disposable.value) {
650 return;
651 }
652 this._disposable.value = value;
653 }
654 > map.ts ×97
655 > dispose() {
656 this._isDisposed = true;
657 this._disposable.dispose();
658 }
659 > } map.ts ×97
660 >
661 > export class RefCountedDisposable {
662 >
663 > private _counter: number = 1;
664 >
665 > constructor(
666 private readonly _disposable: IDisposable,
667 ) { }
668 > map.ts ×97
669 > acquire() {
670 this._counter++;
671 return this;
672 }
673 > map.ts ×97
674 > release() {
675 if (--this._counter === 0) {
676 this._disposable.dispose();
677 }
678 return this;
679 }
680 > } map.ts ×97
681 >
682 > export interface IReference<T> extends IDisposable {
683 > readonly object: T;
684 > }
685 >
686 > export abstract class ReferenceCollection<T> {
688 > private readonly references: Map<string, { readonly object: T; counter: number }> = new Map();
689 > map.ts ×97
690 > acquire(key: string, ...args: unknown[]): IReference<T> {
691 > let reference = this.references.get(key); lifecycle.ts ×2
692 >
693 > if (!reference) {
694 > reference = { counter: 0, object: this.createReferencedObject(key, ...args) };
695 > this.references.set(key, reference);
696 > }
697 >
698 > const { object } = reference;
699 > const dispose = createSingleCallFunction(() => {
700 > if (--reference.counter === 0) { lifecycle.ts ×1
701 > this.destroyReferencedObject(key, reference.object);
702 > this.references.delete(key);
703 > }
704 > }); lifecycle.ts ×2
705 >
706 > reference.counter++;
707 >
708 > return { object, dispose };
709 > }
710 > map.ts ×97
711 > protected abstract createReferencedObject(key: string, ...args: unknown[]): T;
712 > protected abstract destroyReferencedObject(key: string, object: T): void;
713 > }
714 >
715 > /**
716 > * Unwraps a reference collection of promised values. Makes sure
717 > * references are disposed whenever promises get rejected.
718 > */
719 > export class AsyncReferenceCollection<T> {
720 >
721 > constructor(private referenceCollection: ReferenceCollection<Promise<T>>) { }
722 >
723 > async acquire(key: string, ...args: unknown[]): Promise<IReference<T>> {
724 const ref = this.referenceCollection.acquire(key, ...args);
725
726 try {
727 const object = await ref.object;
728
729 return {
730 object,
731 dispose: () => ref.dispose()
732 };
733 } catch (error) {
734 ref.dispose();
735 throw error;
736 }
737 }
738 > } map.ts ×97
739 >
740 > export class ImmortalReference<T> implements IReference<T> {
741 > constructor(public object: T) { }
742 > dispose(): void { /* noop */ }
743 > }
744 >
745 > export function disposeOnReturn(fn: (store: DisposableStore) => void): void {
746 > const store = new DisposableStore(); lifecycle.ts ×1
747 > try {
748 > fn(store);
749 > } finally {
750 > store.dispose();
751 > }
752 > }
753 > map.ts ×97
754 > /**
755 > * A map the manages the lifecycle of the values that it stores.
756 > */
757 > export class DisposableMap<K, V extends IDisposable = IDisposable> implements IDisposable {
758 >
759 > private readonly _store: Map<K, V>;
760 > private _isDisposed = false;
761 >
762 > constructor(store: Map<K, V> = new Map<K, V>()) {
763 > this._store = store; lifecycle.ts ×4
764 > trackDisposable(this);
765 > }
766 > map.ts ×97
767 > /**
768 > * Disposes of all stored values and mark this object as disposed.
769 > *
770 > * Trying to use this object after it has been disposed of is an error.
771 > */
772 > dispose(): void {
773 > markAsDisposed(this); lifecycle.ts ×4
774 > this._isDisposed = true;
775 > this.clearAndDisposeAll();
776 > }
777 > map.ts ×97
778 > /**
779 > * Disposes of all stored values and clear the map, but DO NOT mark this object as disposed.
780 > */
781 > clearAndDisposeAll(): void {
782 > if (!this._store.size) { lifecycle.ts ×4
783 > return; lifecycle.ts ×1
784 > }
786 > try {
787 > dispose(this._store.values());
788 > } finally {
789 > this._store.clear();
790 > }
792 > map.ts ×97
793 > has(key: K): boolean {
794 > return this._store.has(key); lifecycle.ts ×1
795 > }
796 > map.ts ×97
797 > get size(): number {
798 > return this._store.size; async.ts ×5
799 > }
800 > map.ts ×97
801 > get(key: K): V | undefined {
802 > return this._store.get(key); lifecycle.ts ×1
803 > }
804 > map.ts ×97
805 > set(key: K, value: V, skipDisposeOnOverwrite = false): void {
806 > if (this._isDisposed) { lifecycle.ts ×2
807 console.warn(new Error('Trying to add a disposable to a DisposableMap that has already been disposed of. The added object will be leaked!').stack);
808 }
810 > if (!skipDisposeOnOverwrite) {
811 > this._store.get(key)?.dispose();
812 > }
813 >
814 > this._store.set(key, value);
815 > setParentOfDisposable(value, this);
816 > }
817 > map.ts ×97
818 > /**
819 > * Delete the value stored for `key` from this map and also dispose of it.
820 > */
821 > deleteAndDispose(key: K): void {
822 > this._store.get(key)?.dispose(); lifecycle.ts ×1
823 > this._store.delete(key);
824 > }
825 > map.ts ×97
826 > /**
827 > * Delete the value stored for `key` from this map but return it. The caller is
828 > * responsible for disposing of the value.
829 > */
830 > deleteAndLeak(key: K): V | undefined {
831 > const value = this._store.get(key); sshRemoteAgentHostService.ts ×4
832 > if (value) {
833 > setParentOfDisposable(value, null);
834 > }
835 > this._store.delete(key);
836 > return value;
837 > }
838 > map.ts ×97
839 > keys(): IterableIterator<K> {
840 > return this._store.keys(); lifecycle.ts ×1
841 > }
842 > map.ts ×97
843 > values(): IterableIterator<V> {
844 > return this._store.values(); lifecycle.ts ×1
845 > }
846 > map.ts ×97
847 > [Symbol.iterator](): IterableIterator<[K, V]> {
848 > return this._store[Symbol.iterator](); lifecycle.ts ×1
849 > }
850 > } map.ts ×97
851 >
852 > /**
853 > * A set that manages the lifecycle of the values that it stores.
854 > */
855 > export class DisposableSet<V extends IDisposable = IDisposable> implements IDisposable {
856 >
857 > private readonly _store: Set<V>;
858 > private _isDisposed = false;
859 >
860 > constructor(store: Set<V> = new Set<V>()) {
861 > this._store = store; lifecycle.ts ×4
862 > trackDisposable(this);
863 > }
864 > map.ts ×97
865 > /**
866 > * Disposes of all stored values and mark this object as disposed.
867 > *
868 > * Trying to use this object after it has been disposed of is an error.
869 > */
870 > dispose(): void {
871 > markAsDisposed(this); lifecycle.ts ×4
872 > this._isDisposed = true;
873 > this.clearAndDisposeAll();
874 > }
875 > map.ts ×97
876 > /**
877 > * Disposes of all stored values and clear the set, but DO NOT mark this object as disposed.
878 > */
879 > clearAndDisposeAll(): void {
880 > if (!this._store.size) { lifecycle.ts ×4
881 > return; lifecycle.ts ×2
882 > }
884 > try {
885 > dispose(this._store.values());
886 > } finally {
887 > this._store.clear();
888 > }
890 > map.ts ×97
891 > has(value: V): boolean {
892 > return this._store.has(value); lifecycle.ts ×1
893 > }
894 > map.ts ×97
895 > get size(): number {
896 > return this._store.size; lifecycle.ts ×1
897 > }
898 > map.ts ×97
899 > add(value: V): void {
900 > if (this._isDisposed) { lifecycle.ts ×3
901 console.warn(new Error('Trying to add a disposable to a DisposableSet that has already been disposed of. The added object will be leaked!').stack);
902 }
904 > this._store.add(value);
905 > setParentOfDisposable(value, this);
906 > }
907 > map.ts ×97
908 > /**
909 > * Delete the value from this set and also dispose of it.
910 > */
911 > deleteAndDispose(value: V): void {
912 > if (this._store.delete(value)) { lifecycle.ts ×1
913 > value.dispose();
914 > }
915 > }
916 > map.ts ×97
917 > /**
918 > * Delete the value from this set but return it. The caller is
919 > * responsible for disposing of the value.
920 > */
921 > deleteAndLeak(value: V): V | undefined {
922 > if (this._store.delete(value)) { lifecycle.ts ×2
923 > setParentOfDisposable(value, null); lifecycle.ts ×1
924 > return value;
925 > }
926 > return undefined; lifecycle.ts ×2
928 > map.ts ×97
929 > values(): IterableIterator<V> {
930 > return this._store.values(); lifecycle.ts ×1
931 > }
932 > map.ts ×97
933 > [Symbol.iterator](): IterableIterator<V> {
934 > return this._store[Symbol.iterator](); lifecycle.ts ×1
935 > }
936 > } map.ts ×97
937 >
938 > /**
939 > * Call `then` on a Promise, unless the returned disposable is disposed.
940 > */
941 > export function thenIfNotDisposed<T>(promise: Promise<T>, then: (result: T) => void): IDisposable {
942 > let disposed = false; lifecycle.ts ×2
943 > promise.then(result => {
944 > if (disposed) {
945 > return; lifecycle.ts ×1
946 > }
947 > then(result); lifecycle.ts ×1
948 > }); lifecycle.ts ×2
949 > return toDisposable(() => {
950 > disposed = true;
951 > });
952 > }
953 > map.ts ×97
954 > /**
955 > * Call `then` on a promise that resolves to a {@link IDisposable}, then either register the
956 > * disposable or register it to the {@link DisposableStore}, depending on whether the store is
957 > * disposed or not.
958 > */
959 > export function thenRegisterOrDispose<T extends IDisposable>(promise: Promise<T>, store: DisposableStore): Promise<T> {
960 return promise.then(disposable => {
961 if (store.isDisposed) {
962 disposable.dispose();
963 } else {
964 store.add(disposable);
965 }
966 return disposable;
967 });
968 }
969 > map.ts ×97
970 > export class DisposableResourceMap<V extends IDisposable = IDisposable> extends DisposableMap<URI, V> {
971 > constructor() {
972 > super(new ResourceMap()); lifecycle.ts ×1
973 > }
974 > } map.ts ×97