lifecycle.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /*---------------------------------------------------------------------------------------------
  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. import { once } from './functional.js';
  6. import { Iterable } from './iterator.js';
  7. // #region Disposable Tracking
  8. /**
  9. * Enables logging of potentially leaked disposables.
  10. *
  11. * A disposable is considered leaked if it is not disposed or not registered as the child of
  12. * another disposable. This tracking is very simple an only works for classes that either
  13. * extend Disposable or use a DisposableStore. This means there are a lot of false positives.
  14. */
  15. const TRACK_DISPOSABLES = false;
  16. let disposableTracker = null;
  17. export function setDisposableTracker(tracker) {
  18. disposableTracker = tracker;
  19. }
  20. if (TRACK_DISPOSABLES) {
  21. const __is_disposable_tracked__ = '__is_disposable_tracked__';
  22. setDisposableTracker(new class {
  23. trackDisposable(x) {
  24. const stack = new Error('Potentially leaked disposable').stack;
  25. setTimeout(() => {
  26. if (!x[__is_disposable_tracked__]) {
  27. console.log(stack);
  28. }
  29. }, 3000);
  30. }
  31. setParent(child, parent) {
  32. if (child && child !== Disposable.None) {
  33. try {
  34. child[__is_disposable_tracked__] = true;
  35. }
  36. catch (_a) {
  37. // noop
  38. }
  39. }
  40. }
  41. markAsDisposed(disposable) {
  42. if (disposable && disposable !== Disposable.None) {
  43. try {
  44. disposable[__is_disposable_tracked__] = true;
  45. }
  46. catch (_a) {
  47. // noop
  48. }
  49. }
  50. }
  51. markAsSingleton(disposable) { }
  52. });
  53. }
  54. function trackDisposable(x) {
  55. disposableTracker === null || disposableTracker === void 0 ? void 0 : disposableTracker.trackDisposable(x);
  56. return x;
  57. }
  58. function markAsDisposed(disposable) {
  59. disposableTracker === null || disposableTracker === void 0 ? void 0 : disposableTracker.markAsDisposed(disposable);
  60. }
  61. function setParentOfDisposable(child, parent) {
  62. disposableTracker === null || disposableTracker === void 0 ? void 0 : disposableTracker.setParent(child, parent);
  63. }
  64. function setParentOfDisposables(children, parent) {
  65. if (!disposableTracker) {
  66. return;
  67. }
  68. for (const child of children) {
  69. disposableTracker.setParent(child, parent);
  70. }
  71. }
  72. /**
  73. * Indicates that the given object is a singleton which does not need to be disposed.
  74. */
  75. export function markAsSingleton(singleton) {
  76. disposableTracker === null || disposableTracker === void 0 ? void 0 : disposableTracker.markAsSingleton(singleton);
  77. return singleton;
  78. }
  79. /**
  80. * Check if `thing` is {@link IDisposable disposable}.
  81. */
  82. export function isDisposable(thing) {
  83. return typeof thing.dispose === 'function' && thing.dispose.length === 0;
  84. }
  85. export function dispose(arg) {
  86. if (Iterable.is(arg)) {
  87. const errors = [];
  88. for (const d of arg) {
  89. if (d) {
  90. try {
  91. d.dispose();
  92. }
  93. catch (e) {
  94. errors.push(e);
  95. }
  96. }
  97. }
  98. if (errors.length === 1) {
  99. throw errors[0];
  100. }
  101. else if (errors.length > 1) {
  102. throw new AggregateError(errors, 'Encountered errors while disposing of store');
  103. }
  104. return Array.isArray(arg) ? [] : arg;
  105. }
  106. else if (arg) {
  107. arg.dispose();
  108. return arg;
  109. }
  110. }
  111. /**
  112. * Combine multiple disposable values into a single {@link IDisposable}.
  113. */
  114. export function combinedDisposable(...disposables) {
  115. const parent = toDisposable(() => dispose(disposables));
  116. setParentOfDisposables(disposables, parent);
  117. return parent;
  118. }
  119. /**
  120. * Turn a function that implements dispose into an {@link IDisposable}.
  121. */
  122. export function toDisposable(fn) {
  123. const self = trackDisposable({
  124. dispose: once(() => {
  125. markAsDisposed(self);
  126. fn();
  127. })
  128. });
  129. return self;
  130. }
  131. /**
  132. * Manages a collection of disposable values.
  133. *
  134. * This is the preferred way to manage multiple disposables. A `DisposableStore` is safer to work with than an
  135. * `IDisposable[]` as it considers edge cases, such as registering the same value multiple times or adding an item to a
  136. * store that has already been disposed of.
  137. */
  138. export class DisposableStore {
  139. constructor() {
  140. this._toDispose = new Set();
  141. this._isDisposed = false;
  142. trackDisposable(this);
  143. }
  144. /**
  145. * Dispose of all registered disposables and mark this object as disposed.
  146. *
  147. * Any future disposables added to this object will be disposed of on `add`.
  148. */
  149. dispose() {
  150. if (this._isDisposed) {
  151. return;
  152. }
  153. markAsDisposed(this);
  154. this._isDisposed = true;
  155. this.clear();
  156. }
  157. /**
  158. * @return `true` if this object has been disposed of.
  159. */
  160. get isDisposed() {
  161. return this._isDisposed;
  162. }
  163. /**
  164. * Dispose of all registered disposables but do not mark this object as disposed.
  165. */
  166. clear() {
  167. if (this._toDispose.size === 0) {
  168. return;
  169. }
  170. try {
  171. dispose(this._toDispose);
  172. }
  173. finally {
  174. this._toDispose.clear();
  175. }
  176. }
  177. /**
  178. * Add a new {@link IDisposable disposable} to the collection.
  179. */
  180. add(o) {
  181. if (!o) {
  182. return o;
  183. }
  184. if (o === this) {
  185. throw new Error('Cannot register a disposable on itself!');
  186. }
  187. setParentOfDisposable(o, this);
  188. if (this._isDisposed) {
  189. if (!DisposableStore.DISABLE_DISPOSED_WARNING) {
  190. 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);
  191. }
  192. }
  193. else {
  194. this._toDispose.add(o);
  195. }
  196. return o;
  197. }
  198. }
  199. DisposableStore.DISABLE_DISPOSED_WARNING = false;
  200. /**
  201. * Abstract base class for a {@link IDisposable disposable} object.
  202. *
  203. * Subclasses can {@linkcode _register} disposables that will be automatically cleaned up when this object is disposed of.
  204. */
  205. export class Disposable {
  206. constructor() {
  207. this._store = new DisposableStore();
  208. trackDisposable(this);
  209. setParentOfDisposable(this._store, this);
  210. }
  211. dispose() {
  212. markAsDisposed(this);
  213. this._store.dispose();
  214. }
  215. /**
  216. * Adds `o` to the collection of disposables managed by this object.
  217. */
  218. _register(o) {
  219. if (o === this) {
  220. throw new Error('Cannot register a disposable on itself!');
  221. }
  222. return this._store.add(o);
  223. }
  224. }
  225. /**
  226. * A disposable that does nothing when it is disposed of.
  227. *
  228. * TODO: This should not be a static property.
  229. */
  230. Disposable.None = Object.freeze({ dispose() { } });
  231. /**
  232. * Manages the lifecycle of a disposable value that may be changed.
  233. *
  234. * This ensures that when the disposable value is changed, the previously held disposable is disposed of. You can
  235. * also register a `MutableDisposable` on a `Disposable` to ensure it is automatically cleaned up.
  236. */
  237. export class MutableDisposable {
  238. constructor() {
  239. this._isDisposed = false;
  240. trackDisposable(this);
  241. }
  242. get value() {
  243. return this._isDisposed ? undefined : this._value;
  244. }
  245. set value(value) {
  246. var _a;
  247. if (this._isDisposed || value === this._value) {
  248. return;
  249. }
  250. (_a = this._value) === null || _a === void 0 ? void 0 : _a.dispose();
  251. if (value) {
  252. setParentOfDisposable(value, this);
  253. }
  254. this._value = value;
  255. }
  256. /**
  257. * Resets the stored value and disposed of the previously stored value.
  258. */
  259. clear() {
  260. this.value = undefined;
  261. }
  262. dispose() {
  263. var _a;
  264. this._isDisposed = true;
  265. markAsDisposed(this);
  266. (_a = this._value) === null || _a === void 0 ? void 0 : _a.dispose();
  267. this._value = undefined;
  268. }
  269. }
  270. export class RefCountedDisposable {
  271. constructor(_disposable) {
  272. this._disposable = _disposable;
  273. this._counter = 1;
  274. }
  275. acquire() {
  276. this._counter++;
  277. return this;
  278. }
  279. release() {
  280. if (--this._counter === 0) {
  281. this._disposable.dispose();
  282. }
  283. return this;
  284. }
  285. }
  286. /**
  287. * A safe disposable can be `unset` so that a leaked reference (listener)
  288. * can be cut-off.
  289. */
  290. export class SafeDisposable {
  291. constructor() {
  292. this.dispose = () => { };
  293. this.unset = () => { };
  294. this.isset = () => false;
  295. trackDisposable(this);
  296. }
  297. set(fn) {
  298. let callback = fn;
  299. this.unset = () => callback = undefined;
  300. this.isset = () => callback !== undefined;
  301. this.dispose = () => {
  302. if (callback) {
  303. callback();
  304. callback = undefined;
  305. markAsDisposed(this);
  306. }
  307. };
  308. return this;
  309. }
  310. }
  311. export class ImmortalReference {
  312. constructor(object) {
  313. this.object = object;
  314. }
  315. dispose() { }
  316. }
  317. /**
  318. * A map the manages the lifecycle of the values that it stores.
  319. */
  320. export class DisposableMap {
  321. constructor() {
  322. this._store = new Map();
  323. this._isDisposed = false;
  324. trackDisposable(this);
  325. }
  326. /**
  327. * Disposes of all stored values and mark this object as disposed.
  328. *
  329. * Trying to use this object after it has been disposed of is an error.
  330. */
  331. dispose() {
  332. markAsDisposed(this);
  333. this._isDisposed = true;
  334. this.clearAndDisposeAll();
  335. }
  336. /**
  337. * Disposes of all stored values and clear the map, but DO NOT mark this object as disposed.
  338. */
  339. clearAndDisposeAll() {
  340. if (!this._store.size) {
  341. return;
  342. }
  343. try {
  344. dispose(this._store.values());
  345. }
  346. finally {
  347. this._store.clear();
  348. }
  349. }
  350. get(key) {
  351. return this._store.get(key);
  352. }
  353. set(key, value, skipDisposeOnOverwrite = false) {
  354. var _a;
  355. if (this._isDisposed) {
  356. 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);
  357. }
  358. if (!skipDisposeOnOverwrite) {
  359. (_a = this._store.get(key)) === null || _a === void 0 ? void 0 : _a.dispose();
  360. }
  361. this._store.set(key, value);
  362. }
  363. /**
  364. * Delete the value stored for `key` from this map and also dispose of it.
  365. */
  366. deleteAndDispose(key) {
  367. var _a;
  368. (_a = this._store.get(key)) === null || _a === void 0 ? void 0 : _a.dispose();
  369. this._store.delete(key);
  370. }
  371. [Symbol.iterator]() {
  372. return this._store[Symbol.iterator]();
  373. }
  374. }