2020-05-23 01:11:14 +00:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
// Modeled very closely on the AbortController implementation
|
|
|
|
// in https://github.com/mysticatea/abort-controller (MIT license)
|
|
|
|
|
|
|
|
const {
|
2020-09-27 15:39:01 +00:00
|
|
|
ObjectAssign,
|
|
|
|
ObjectDefineProperties,
|
2020-11-12 14:26:06 +00:00
|
|
|
ObjectSetPrototypeOf,
|
2020-11-14 11:46:29 +00:00
|
|
|
ObjectDefineProperty,
|
2021-11-20 20:55:05 +00:00
|
|
|
SafeFinalizationRegistry,
|
|
|
|
SafeSet,
|
2020-05-23 01:11:14 +00:00
|
|
|
Symbol,
|
2020-11-14 11:46:29 +00:00
|
|
|
SymbolToStringTag,
|
2021-11-20 20:55:05 +00:00
|
|
|
WeakRef,
|
2020-05-23 01:11:14 +00:00
|
|
|
} = primordials;
|
|
|
|
|
|
|
|
const {
|
2020-10-30 22:38:26 +00:00
|
|
|
defineEventHandler,
|
2020-05-23 01:11:14 +00:00
|
|
|
EventTarget,
|
2020-10-26 11:56:48 +00:00
|
|
|
Event,
|
2021-11-20 20:55:05 +00:00
|
|
|
kTrustEvent,
|
|
|
|
kNewListener,
|
|
|
|
kRemoveListener,
|
2020-05-23 01:11:14 +00:00
|
|
|
} = require('internal/event_target');
|
|
|
|
const {
|
|
|
|
customInspectSymbol,
|
2022-02-11 17:30:47 +00:00
|
|
|
kEnumerableProperty,
|
2022-07-29 18:31:24 +00:00
|
|
|
kEmptyObject,
|
2020-05-23 01:11:14 +00:00
|
|
|
} = require('internal/util');
|
|
|
|
const { inspect } = require('internal/util/inspect');
|
2021-03-11 22:25:45 +00:00
|
|
|
const {
|
|
|
|
codes: {
|
2021-07-28 01:41:18 +00:00
|
|
|
ERR_ILLEGAL_CONSTRUCTOR,
|
2022-07-29 18:31:24 +00:00
|
|
|
ERR_INVALID_ARG_TYPE,
|
2021-03-11 22:25:45 +00:00
|
|
|
ERR_INVALID_THIS,
|
|
|
|
}
|
|
|
|
} = require('internal/errors');
|
2020-05-23 01:11:14 +00:00
|
|
|
|
2021-11-20 20:55:05 +00:00
|
|
|
const {
|
|
|
|
validateUint32,
|
|
|
|
} = require('internal/validators');
|
|
|
|
|
|
|
|
const {
|
|
|
|
DOMException,
|
|
|
|
} = internalBinding('messaging');
|
|
|
|
|
|
|
|
const {
|
|
|
|
clearTimeout,
|
|
|
|
setTimeout,
|
|
|
|
} = require('timers');
|
|
|
|
|
2021-12-01 16:18:37 +00:00
|
|
|
const {
|
|
|
|
messaging_deserialize_symbol: kDeserialize,
|
|
|
|
messaging_transfer_symbol: kTransfer,
|
|
|
|
messaging_transfer_list_symbol: kTransferList
|
|
|
|
} = internalBinding('symbols');
|
2021-11-20 20:55:05 +00:00
|
|
|
|
2021-12-01 16:18:37 +00:00
|
|
|
let _MessageChannel;
|
|
|
|
let makeTransferable;
|
|
|
|
|
|
|
|
// Loading the MessageChannel and makeTransferable have to be done lazily
|
|
|
|
// because otherwise we'll end up with a require cycle that ends up with
|
|
|
|
// an incomplete initialization of abort_controller.
|
|
|
|
|
|
|
|
function lazyMessageChannel() {
|
|
|
|
_MessageChannel ??= require('internal/worker/io').MessageChannel;
|
|
|
|
return new _MessageChannel();
|
|
|
|
}
|
|
|
|
|
|
|
|
function lazyMakeTransferable(obj) {
|
|
|
|
makeTransferable ??=
|
|
|
|
require('internal/worker/js_transferable').makeTransferable;
|
|
|
|
return makeTransferable(obj);
|
|
|
|
}
|
2021-11-20 20:55:05 +00:00
|
|
|
|
|
|
|
const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout);
|
2021-12-01 16:18:37 +00:00
|
|
|
const timeOutSignals = new SafeSet();
|
|
|
|
|
|
|
|
const kAborted = Symbol('kAborted');
|
|
|
|
const kReason = Symbol('kReason');
|
|
|
|
const kCloneData = Symbol('kCloneData');
|
|
|
|
const kTimeout = Symbol('kTimeout');
|
2022-07-29 18:31:24 +00:00
|
|
|
const kMakeTransferable = Symbol('kMakeTransferable');
|
2020-05-23 01:11:14 +00:00
|
|
|
|
|
|
|
function customInspect(self, obj, depth, options) {
|
|
|
|
if (depth < 0)
|
|
|
|
return self;
|
|
|
|
|
2020-09-27 15:39:01 +00:00
|
|
|
const opts = ObjectAssign({}, options, {
|
2020-05-23 01:11:14 +00:00
|
|
|
depth: options.depth === null ? null : options.depth - 1
|
|
|
|
});
|
|
|
|
|
|
|
|
return `${self.constructor.name} ${inspect(obj, opts)}`;
|
|
|
|
}
|
|
|
|
|
2021-03-11 22:25:45 +00:00
|
|
|
function validateAbortSignal(obj) {
|
|
|
|
if (obj?.[kAborted] === undefined)
|
|
|
|
throw new ERR_INVALID_THIS('AbortSignal');
|
|
|
|
}
|
|
|
|
|
2021-11-20 20:55:05 +00:00
|
|
|
// Because the AbortSignal timeout cannot be canceled, we don't want the
|
|
|
|
// presence of the timer alone to keep the AbortSignal from being garbage
|
|
|
|
// collected if it otherwise no longer accessible. We also don't want the
|
|
|
|
// timer to keep the Node.js process open on it's own. Therefore, we wrap
|
|
|
|
// the AbortSignal in a WeakRef and have the setTimeout callback close
|
|
|
|
// over the WeakRef rather than directly over the AbortSignal, and we unref
|
|
|
|
// the created timer object. Separately, we add the signal to a
|
|
|
|
// FinalizerRegistry that will clear the timeout when the signal is gc'd.
|
|
|
|
function setWeakAbortSignalTimeout(weakRef, delay) {
|
|
|
|
const timeout = setTimeout(() => {
|
|
|
|
const signal = weakRef.deref();
|
|
|
|
if (signal !== undefined) {
|
|
|
|
timeOutSignals.delete(signal);
|
|
|
|
abortSignal(
|
|
|
|
signal,
|
|
|
|
new DOMException(
|
|
|
|
'The operation was aborted due to timeout',
|
|
|
|
'TimeoutError'));
|
|
|
|
}
|
|
|
|
}, delay);
|
|
|
|
timeout.unref();
|
|
|
|
return timeout;
|
|
|
|
}
|
|
|
|
|
2020-05-23 01:11:14 +00:00
|
|
|
class AbortSignal extends EventTarget {
|
2020-11-12 14:26:06 +00:00
|
|
|
constructor() {
|
2021-07-28 01:41:18 +00:00
|
|
|
throw new ERR_ILLEGAL_CONSTRUCTOR();
|
2020-11-12 14:26:06 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 16:27:32 +00:00
|
|
|
/**
|
|
|
|
* @type {boolean}
|
|
|
|
*/
|
2021-03-11 22:25:45 +00:00
|
|
|
get aborted() {
|
|
|
|
validateAbortSignal(this);
|
|
|
|
return !!this[kAborted];
|
|
|
|
}
|
2020-05-23 01:11:14 +00:00
|
|
|
|
2021-11-13 16:27:32 +00:00
|
|
|
/**
|
|
|
|
* @type {any}
|
|
|
|
*/
|
|
|
|
get reason() {
|
|
|
|
validateAbortSignal(this);
|
|
|
|
return this[kReason];
|
|
|
|
}
|
|
|
|
|
2021-11-24 15:28:30 +00:00
|
|
|
throwIfAborted() {
|
|
|
|
if (this.aborted) {
|
|
|
|
throw this.reason;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 01:11:14 +00:00
|
|
|
[customInspectSymbol](depth, options) {
|
|
|
|
return customInspect(this, {
|
|
|
|
aborted: this.aborted
|
|
|
|
}, depth, options);
|
|
|
|
}
|
2021-03-10 16:59:52 +00:00
|
|
|
|
2021-11-13 16:27:32 +00:00
|
|
|
/**
|
|
|
|
* @param {any} reason
|
|
|
|
* @returns {AbortSignal}
|
|
|
|
*/
|
2021-11-24 15:28:30 +00:00
|
|
|
static abort(
|
|
|
|
reason = new DOMException('This operation was aborted', 'AbortError')) {
|
2022-07-29 18:31:24 +00:00
|
|
|
return createAbortSignal({ aborted: true, reason });
|
2021-03-10 16:59:52 +00:00
|
|
|
}
|
2021-11-20 20:55:05 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @param {number} delay
|
|
|
|
* @returns {AbortSignal}
|
|
|
|
*/
|
|
|
|
static timeout(delay) {
|
|
|
|
validateUint32(delay, 'delay', true);
|
|
|
|
const signal = createAbortSignal();
|
|
|
|
signal[kTimeout] = true;
|
|
|
|
clearTimeoutRegistry.register(
|
|
|
|
signal,
|
|
|
|
setWeakAbortSignalTimeout(new WeakRef(signal), delay));
|
|
|
|
return signal;
|
|
|
|
}
|
|
|
|
|
|
|
|
[kNewListener](size, type, listener, once, capture, passive, weak) {
|
|
|
|
super[kNewListener](size, type, listener, once, capture, passive, weak);
|
|
|
|
if (this[kTimeout] &&
|
|
|
|
type === 'abort' &&
|
|
|
|
!this.aborted &&
|
|
|
|
!weak &&
|
|
|
|
size === 1) {
|
|
|
|
// If this is a timeout signal, and we're adding a non-weak abort
|
|
|
|
// listener, then we don't want it to be gc'd while the listener
|
|
|
|
// is attached and the timer still hasn't fired. So, we retain a
|
|
|
|
// strong ref that is held for as long as the listener is registered.
|
|
|
|
timeOutSignals.add(this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
[kRemoveListener](size, type, listener, capture) {
|
|
|
|
super[kRemoveListener](size, type, listener, capture);
|
|
|
|
if (this[kTimeout] && type === 'abort' && size === 0) {
|
|
|
|
timeOutSignals.delete(this);
|
|
|
|
}
|
|
|
|
}
|
2021-12-01 16:18:37 +00:00
|
|
|
|
|
|
|
[kTransfer]() {
|
|
|
|
validateAbortSignal(this);
|
|
|
|
const aborted = this.aborted;
|
|
|
|
if (aborted) {
|
|
|
|
const reason = this.reason;
|
|
|
|
return {
|
|
|
|
data: { aborted, reason },
|
|
|
|
deserializeInfo: 'internal/abort_controller:ClonedAbortSignal',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const { port1, port2 } = this[kCloneData];
|
|
|
|
this[kCloneData] = undefined;
|
|
|
|
|
|
|
|
this.addEventListener('abort', () => {
|
|
|
|
port1.postMessage(this.reason);
|
|
|
|
port1.close();
|
|
|
|
}, { once: true });
|
|
|
|
|
|
|
|
return {
|
|
|
|
data: { port: port2 },
|
|
|
|
deserializeInfo: 'internal/abort_controller:ClonedAbortSignal',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
[kTransferList]() {
|
|
|
|
if (!this.aborted) {
|
|
|
|
const { port1, port2 } = lazyMessageChannel();
|
|
|
|
port1.unref();
|
|
|
|
port2.unref();
|
|
|
|
this[kCloneData] = {
|
|
|
|
port1,
|
|
|
|
port2,
|
|
|
|
};
|
|
|
|
return [port2];
|
|
|
|
}
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
[kDeserialize]({ aborted, reason, port }) {
|
|
|
|
if (aborted) {
|
|
|
|
this[kAborted] = aborted;
|
|
|
|
this[kReason] = reason;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
port.onmessage = ({ data }) => {
|
|
|
|
abortSignal(this, data);
|
|
|
|
port.close();
|
|
|
|
port.onmessage = undefined;
|
|
|
|
};
|
|
|
|
// The receiving port, by itself, should never keep the event loop open.
|
|
|
|
// The unref() has to be called *after* setting the onmessage handler.
|
|
|
|
port.unref();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function ClonedAbortSignal() {
|
2022-07-29 18:31:24 +00:00
|
|
|
return createAbortSignal({ transferable: true });
|
2020-05-23 01:11:14 +00:00
|
|
|
}
|
2021-12-01 16:18:37 +00:00
|
|
|
ClonedAbortSignal.prototype[kDeserialize] = () => {};
|
2020-05-23 01:11:14 +00:00
|
|
|
|
2020-09-27 15:39:01 +00:00
|
|
|
ObjectDefineProperties(AbortSignal.prototype, {
|
2022-02-11 17:30:47 +00:00
|
|
|
aborted: kEnumerableProperty,
|
2020-05-23 01:11:14 +00:00
|
|
|
});
|
|
|
|
|
2020-11-14 11:46:29 +00:00
|
|
|
ObjectDefineProperty(AbortSignal.prototype, SymbolToStringTag, {
|
2022-06-03 08:23:58 +00:00
|
|
|
__proto__: null,
|
2020-11-14 11:46:29 +00:00
|
|
|
writable: false,
|
|
|
|
enumerable: false,
|
|
|
|
configurable: true,
|
|
|
|
value: 'AbortSignal',
|
|
|
|
});
|
|
|
|
|
2020-11-02 20:59:54 +00:00
|
|
|
defineEventHandler(AbortSignal.prototype, 'abort');
|
|
|
|
|
2022-07-29 18:31:24 +00:00
|
|
|
/**
|
|
|
|
* @param {{
|
|
|
|
* aborted? : boolean,
|
|
|
|
* reason? : any,
|
|
|
|
* transferable? : boolean
|
|
|
|
* }} [init]
|
|
|
|
* @returns {AbortSignal}
|
|
|
|
*/
|
|
|
|
function createAbortSignal(init = kEmptyObject) {
|
|
|
|
const {
|
|
|
|
aborted = false,
|
|
|
|
reason = undefined,
|
|
|
|
transferable = false,
|
|
|
|
} = init;
|
2020-11-12 14:26:06 +00:00
|
|
|
const signal = new EventTarget();
|
|
|
|
ObjectSetPrototypeOf(signal, AbortSignal.prototype);
|
2021-03-10 16:59:52 +00:00
|
|
|
signal[kAborted] = aborted;
|
2021-11-13 16:27:32 +00:00
|
|
|
signal[kReason] = reason;
|
2022-07-29 18:31:24 +00:00
|
|
|
return transferable ? lazyMakeTransferable(signal) : signal;
|
2020-11-12 14:26:06 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 16:27:32 +00:00
|
|
|
function abortSignal(signal, reason) {
|
2020-05-23 01:11:14 +00:00
|
|
|
if (signal[kAborted]) return;
|
|
|
|
signal[kAborted] = true;
|
2021-11-13 16:27:32 +00:00
|
|
|
signal[kReason] = reason;
|
2020-10-26 11:56:48 +00:00
|
|
|
const event = new Event('abort', {
|
|
|
|
[kTrustEvent]: true
|
|
|
|
});
|
2020-05-23 01:11:14 +00:00
|
|
|
signal.dispatchEvent(event);
|
|
|
|
}
|
|
|
|
|
|
|
|
class AbortController {
|
2022-07-16 06:07:53 +00:00
|
|
|
#signal = createAbortSignal();
|
2020-05-23 01:11:14 +00:00
|
|
|
|
2021-11-13 16:27:32 +00:00
|
|
|
/**
|
|
|
|
* @type {AbortSignal}
|
|
|
|
*/
|
2021-03-11 22:25:45 +00:00
|
|
|
get signal() {
|
2022-07-16 06:07:53 +00:00
|
|
|
return this.#signal;
|
2021-03-11 22:25:45 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 16:27:32 +00:00
|
|
|
/**
|
|
|
|
* @param {any} reason
|
|
|
|
*/
|
2021-11-24 15:28:30 +00:00
|
|
|
abort(reason = new DOMException('This operation was aborted', 'AbortError')) {
|
2022-07-16 06:07:53 +00:00
|
|
|
abortSignal(this.#signal, reason);
|
2021-03-11 22:25:45 +00:00
|
|
|
}
|
2020-05-23 01:11:14 +00:00
|
|
|
|
|
|
|
[customInspectSymbol](depth, options) {
|
|
|
|
return customInspect(this, {
|
|
|
|
signal: this.signal
|
|
|
|
}, depth, options);
|
|
|
|
}
|
2022-07-29 18:31:24 +00:00
|
|
|
|
|
|
|
static [kMakeTransferable]() {
|
|
|
|
const controller = new AbortController();
|
|
|
|
controller.#signal = transferableAbortSignal(controller.#signal);
|
|
|
|
return controller;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Enables the AbortSignal to be transferable using structuredClone/postMessage.
|
|
|
|
* @param {AbortSignal} signal
|
|
|
|
* @returns {AbortSignal}
|
|
|
|
*/
|
|
|
|
function transferableAbortSignal(signal) {
|
|
|
|
if (signal?.[kAborted] === undefined)
|
|
|
|
throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
|
|
|
|
return lazyMakeTransferable(signal);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Creates an AbortController with a transferable AbortSignal
|
|
|
|
*/
|
|
|
|
function transferableAbortController() {
|
|
|
|
return AbortController[kMakeTransferable]();
|
2020-05-23 01:11:14 +00:00
|
|
|
}
|
|
|
|
|
2020-09-27 15:39:01 +00:00
|
|
|
ObjectDefineProperties(AbortController.prototype, {
|
2022-02-11 17:30:47 +00:00
|
|
|
signal: kEnumerableProperty,
|
|
|
|
abort: kEnumerableProperty,
|
2020-05-23 01:11:14 +00:00
|
|
|
});
|
|
|
|
|
2020-11-14 11:46:29 +00:00
|
|
|
ObjectDefineProperty(AbortController.prototype, SymbolToStringTag, {
|
2022-06-03 08:23:58 +00:00
|
|
|
__proto__: null,
|
2020-11-14 11:46:29 +00:00
|
|
|
writable: false,
|
|
|
|
enumerable: false,
|
|
|
|
configurable: true,
|
|
|
|
value: 'AbortController',
|
|
|
|
});
|
|
|
|
|
2020-05-23 01:11:14 +00:00
|
|
|
module.exports = {
|
2021-06-16 19:44:22 +00:00
|
|
|
kAborted,
|
2020-05-23 01:11:14 +00:00
|
|
|
AbortController,
|
|
|
|
AbortSignal,
|
2021-12-01 16:18:37 +00:00
|
|
|
ClonedAbortSignal,
|
2022-07-29 18:31:24 +00:00
|
|
|
transferableAbortSignal,
|
|
|
|
transferableAbortController,
|
2020-05-23 01:11:14 +00:00
|
|
|
};
|