UNPKG

16.3 kBJavaScriptView Raw
1"use strict";
2/*! *****************************************************************************
3Copyright (c) Microsoft Corporation.
4Licensed under the Apache License, Version 2.0.
5
6See LICENSE file in the project root for details.
7***************************************************************************** */
8Object.defineProperty(exports, "__esModule", { value: true });
9const cancelable_1 = require("@esfx/cancelable");
10const disposable_1 = require("@esfx/disposable");
11const list_1 = require("./list");
12const utils_1 = require("./utils");
13/**
14 * Signals a CancellationToken that it should be canceled.
15 */
16class CancellationTokenSource {
17 /**
18 * Initializes a new instance of a CancellationTokenSource.
19 *
20 * @param linkedTokens An optional iterable of tokens to which to link this source.
21 */
22 constructor(linkedTokens) {
23 this._state = "open";
24 this._token = undefined;
25 this._registrations = undefined;
26 this._linkingRegistrations = undefined;
27 if (!utils_1.isIterable(linkedTokens, /*optional*/ true))
28 throw new TypeError("Object not iterable: linkedTokens.");
29 if (linkedTokens) {
30 for (const linkedToken of linkedTokens) {
31 if (!cancelable_1.Cancelable.hasInstance(linkedToken)) {
32 throw new TypeError("CancellationToken expected.");
33 }
34 const token = CancellationToken.from(linkedToken);
35 if (token.cancellationRequested) {
36 this._state = "cancellationRequested";
37 this._unlink();
38 break;
39 }
40 else if (token.canBeCanceled) {
41 if (this._linkingRegistrations === undefined) {
42 this._linkingRegistrations = [];
43 }
44 this._linkingRegistrations.push(token.register(() => this.cancel()));
45 }
46 }
47 }
48 }
49 /**
50 * Gets a CancellationToken linked to this source.
51 */
52 get token() {
53 if (this._token === undefined) {
54 this._token = new CancellationToken(this);
55 }
56 return this._token;
57 }
58 /*@internal*/ get _currentState() {
59 if (this._state === "open" && this._linkingRegistrations && this._linkingRegistrations.length > 0) {
60 for (const registration of this._linkingRegistrations) {
61 if (registration._cancellationSource &&
62 registration._cancellationSource._currentState === "cancellationRequested") {
63 return "cancellationRequested";
64 }
65 }
66 }
67 return this._state;
68 }
69 /**
70 * Gets a value indicating whether cancellation has been requested.
71 */
72 /*@internal*/ get _cancellationRequested() {
73 return this._currentState === "cancellationRequested";
74 }
75 /**
76 * Gets a value indicating whether the source can be canceled.
77 */
78 /*@internal*/ get _canBeCanceled() {
79 return this._currentState !== "closed";
80 }
81 /**
82 * Cancels the source, evaluating any registered callbacks. If any callback raises an exception,
83 * the exception is propagated to a host specific unhanedle exception mechanism.
84 */
85 cancel() {
86 if (this._state !== "open") {
87 return;
88 }
89 this._state = "cancellationRequested";
90 this._unlink();
91 const registrations = this._registrations;
92 this._registrations = undefined;
93 if (registrations && registrations.size > 0) {
94 for (const registration of registrations) {
95 if (registration._cancellationTarget) {
96 this._executeCallback(registration._cancellationTarget);
97 }
98 }
99 }
100 }
101 /**
102 * Closes the source, preventing the possibility of future cancellation.
103 */
104 close() {
105 if (this._state !== "open") {
106 return;
107 }
108 this._state = "closed";
109 this._unlink();
110 const callbacks = this._registrations;
111 this._registrations = undefined;
112 if (callbacks !== undefined) {
113 // The registration for each callback holds onto the node, the node holds onto the
114 // list, and the list holds all other nodes and callbacks. By clearing the list, the
115 // GC can collect any otherwise unreachable nodes.
116 callbacks.clear();
117 }
118 }
119 /**
120 * Registers a callback to execute when cancellation has been requested. If cancellation has
121 * already been requested, the callback is executed immediately.
122 *
123 * @param callback The callback to register.
124 */
125 /*@internal*/ _register(callback) {
126 if (!utils_1.isFunction(callback))
127 throw new TypeError("Function expected: callback.");
128 if (this._state === "cancellationRequested") {
129 this._executeCallback(callback);
130 return emptyRegistration;
131 }
132 if (this._state === "closed") {
133 return emptyRegistration;
134 }
135 if (this._registrations === undefined) {
136 this._registrations = new list_1.LinkedList();
137 }
138 function unregister() {
139 if (this._cancellationSource === undefined)
140 return;
141 if (this._cancellationSource._registrations) {
142 this._cancellationSource._registrations.deleteNode(node);
143 }
144 this._cancellationSource = undefined;
145 this._cancellationTarget = undefined;
146 }
147 const node = this._registrations.push({
148 _cancellationSource: this,
149 _cancellationTarget: callback,
150 unregister,
151 [disposable_1.Disposable.dispose]: unregister,
152 });
153 return node.value;
154 }
155 /**
156 * Executes the provided callback.
157 *
158 * @param callback The callback to execute.
159 */
160 _executeCallback(callback) {
161 try {
162 callback();
163 }
164 catch (e) {
165 // HostReportError(e)
166 setTimeout(() => { throw e; }, 0);
167 }
168 }
169 /**
170 * Unlinks the source from any linked tokens.
171 */
172 _unlink() {
173 const linkingRegistrations = this._linkingRegistrations;
174 this._linkingRegistrations = undefined;
175 if (linkingRegistrations !== undefined) {
176 for (const linkingRegistration of linkingRegistrations) {
177 linkingRegistration.unregister();
178 }
179 }
180 }
181 // #region Cancelable
182 [cancelable_1.Cancelable.cancelSignal]() { return this.token[cancelable_1.Cancelable.cancelSignal](); }
183 // #endregion Cancelable
184 // #region CancelableSource
185 [cancelable_1.CancelableSource.cancel]() { this.cancel(); }
186}
187exports.CancellationTokenSource = CancellationTokenSource;
188// A source that cannot be canceled.
189const closedSource = new CancellationTokenSource();
190closedSource.close();
191// A source that is already canceled.
192const canceledSource = new CancellationTokenSource();
193canceledSource.cancel();
194const weakCancelableToToken = typeof WeakMap === "function" ? new WeakMap() : undefined;
195const weakTokenToCancelable = typeof WeakMap === "function" ? new WeakMap() : undefined;
196/**
197 * Propagates notifications that operations should be canceled.
198 */
199class CancellationToken {
200 constructor(source) {
201 if (utils_1.isMissing(source)) {
202 this._source = closedSource;
203 }
204 else if (utils_1.isBoolean(source)) {
205 this._source = source ? canceledSource : closedSource;
206 }
207 else {
208 if (!utils_1.isInstance(source, CancellationTokenSource))
209 throw new TypeError("CancellationTokenSource expected: source.");
210 this._source = source;
211 }
212 Object.freeze(this);
213 }
214 /**
215 * Gets a value indicating whether cancellation has been requested.
216 */
217 get cancellationRequested() {
218 return this._source._cancellationRequested;
219 }
220 /**
221 * Gets a value indicating whether the underlying source can be canceled.
222 */
223 get canBeCanceled() {
224 return this._source._canBeCanceled;
225 }
226 /**
227 * Adapts a CancellationToken-like primitive from a different library.
228 */
229 static from(cancelable) {
230 if (cancelable instanceof CancellationToken) {
231 return cancelable;
232 }
233 if (cancelable_1.Cancelable.hasInstance(cancelable)) {
234 const signal = cancelable[cancelable_1.Cancelable.cancelSignal]();
235 if (signal.signaled)
236 return CancellationToken.canceled;
237 let token = weakCancelableToToken && weakCancelableToToken.get(cancelable);
238 if (!token) {
239 const source = new CancellationTokenSource();
240 signal.subscribe(() => source.cancel());
241 token = source.token;
242 if (weakCancelableToToken)
243 weakCancelableToToken.set(cancelable, token);
244 }
245 return token;
246 }
247 if (isVSCodeCancellationTokenLike(cancelable)) {
248 if (cancelable.isCancellationRequested)
249 return CancellationToken.canceled;
250 const source = new CancellationTokenSource();
251 cancelable.onCancellationRequested(() => source.cancel());
252 return source.token;
253 }
254 if (isAbortSignalLike(cancelable)) {
255 if (cancelable.aborted)
256 return CancellationToken.canceled;
257 const source = new CancellationTokenSource();
258 cancelable.addEventListener("abort", () => source.cancel());
259 return source.token;
260 }
261 throw new TypeError("Invalid token.");
262 }
263 /**
264 * Returns a CancellationToken that becomes canceled when **any** of the provided tokens are canceled.
265 * @param tokens An iterable of CancellationToken objects.
266 */
267 static race(tokens) {
268 if (!utils_1.isIterable(tokens))
269 throw new TypeError("Object not iterable: iterable.");
270 const tokensArray = Array.isArray(tokens) ? tokens : [...tokens];
271 return tokensArray.length > 0 ? new CancellationTokenSource(tokensArray).token : CancellationToken.none;
272 }
273 /**
274 * Returns a CancellationToken that becomes canceled when **all** of the provided tokens are canceled.
275 * @param tokens An iterable of CancellationToken objects.
276 */
277 static all(tokens) {
278 if (!utils_1.isIterable(tokens))
279 throw new TypeError("Object not iterable: iterable.");
280 const tokensArray = Array.isArray(tokens) ? tokens : [...tokens];
281 return tokensArray.length > 0 ? new CancellationTokenCountdown(tokensArray).token : CancellationToken.none;
282 }
283 /**
284 * Throws a CancelError if cancellation has been requested.
285 */
286 throwIfCancellationRequested() {
287 if (this.cancellationRequested) {
288 throw new CancelError();
289 }
290 }
291 /**
292 * Registers a callback to execute when cancellation is requested.
293 *
294 * @param callback The callback to register.
295 */
296 register(callback) {
297 return this._source._register(callback);
298 }
299 // #region Cancelable
300 [cancelable_1.Cancelable.cancelSignal]() {
301 let signal = weakTokenToCancelable === null || weakTokenToCancelable === void 0 ? void 0 : weakTokenToCancelable.get(this);
302 if (!signal) {
303 const token = this;
304 signal = {
305 get signaled() { return token.cancellationRequested; },
306 subscribe(onCancellationRequested) {
307 const registration = token.register(onCancellationRequested);
308 return {
309 unsubscribe() { registration.unregister(); },
310 [disposable_1.Disposable.dispose]() { this.unsubscribe(); }
311 };
312 },
313 [cancelable_1.Cancelable.cancelSignal]() {
314 return this;
315 }
316 };
317 weakTokenToCancelable === null || weakTokenToCancelable === void 0 ? void 0 : weakTokenToCancelable.set(this, signal);
318 }
319 return signal;
320 }
321}
322exports.CancellationToken = CancellationToken;
323/**
324 * A token which will never be canceled.
325 */
326CancellationToken.none = new CancellationToken(/*canceled*/ false);
327/**
328 * A token that is already canceled.
329 */
330CancellationToken.canceled = new CancellationToken(/*canceled*/ true);
331/**
332 * An error thrown when an operation is canceled.
333 */
334class CancelError extends Error {
335 constructor(message) {
336 super(message || "Operation was canceled");
337 }
338}
339exports.CancelError = CancelError;
340CancelError.prototype.name = "CancelError";
341const emptyRegistration = Object.create({ unregister() { } });
342function isVSCodeCancellationTokenLike(token) {
343 return typeof token === "object"
344 && token !== null
345 && utils_1.isBoolean(token.isCancellationRequested)
346 && utils_1.isFunction(token.onCancellationRequested);
347}
348function isAbortSignalLike(token) {
349 return typeof token === "object"
350 && token !== null
351 && utils_1.isBoolean(token.aborted)
352 && utils_1.isFunction(token.addEventListener);
353}
354/**
355 * An object that provides a CancellationToken that becomes cancelled when **all** of its
356 * containing tokens are canceled. This is similar to `CancellationToken.all`, except that you are
357 * able to add additional tokens.
358 */
359class CancellationTokenCountdown {
360 constructor(iterable) {
361 this._addedCount = 0;
362 this._signaledCount = 0;
363 this._canBeSignaled = false;
364 this._source = new CancellationTokenSource();
365 this._registrations = [];
366 if (!utils_1.isIterable(iterable, /*optional*/ true))
367 throw new TypeError("Object not iterable: iterable.");
368 if (iterable) {
369 for (const token of iterable) {
370 this.add(token);
371 }
372 }
373 this._canBeSignaled = true;
374 this._checkSignalState();
375 }
376 /**
377 * Gets the number of tokens added to the countdown.
378 */
379 get addedCount() { return this._addedCount; }
380 /**
381 * Gets the number of tokens that have not yet been canceled.
382 */
383 get remainingCount() { return this._addedCount - this._signaledCount; }
384 /**
385 * Gets the CancellationToken for the countdown.
386 */
387 get token() { return this._source.token; }
388 /**
389 * Adds a CancellationToken to the countdown.
390 */
391 add(token) {
392 if (!cancelable_1.Cancelable.hasInstance(token))
393 throw new TypeError("CancellationToken or Cancelable expected.");
394 const ct = CancellationToken.from(token);
395 if (this._source._currentState !== "open")
396 return this;
397 if (ct.cancellationRequested) {
398 this._addedCount++;
399 this._signaledCount++;
400 this._checkSignalState();
401 }
402 else if (ct.canBeCanceled) {
403 this._addedCount++;
404 this._registrations.push(ct.register(() => {
405 this._signaledCount++;
406 this._checkSignalState();
407 }));
408 }
409 return this;
410 }
411 _checkSignalState() {
412 if (!this._canBeSignaled || this._signaledCount < this._addedCount)
413 return;
414 this._canBeSignaled = false;
415 if (this._addedCount > 0) {
416 try {
417 for (const registration of this._registrations) {
418 registration.unregister();
419 }
420 }
421 finally {
422 this._registrations.length = 0;
423 this._source.cancel();
424 }
425 }
426 }
427}
428exports.CancellationTokenCountdown = CancellationTokenCountdown;