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