Skip to content
Permalink
Browse files
lib: refactor transferable AbortSignal
Co-authored-by: James M Snell <jasnell@gmail.com>
PR-URL: #44048
Backport-PR-URL: #44941
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
Reviewed-By: Paolo Insogna <paolo@cowtech.it>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
  • Loading branch information
2 people authored and danielleadams committed Oct 11, 2022
1 parent ce3cb29 commit 3f20e5b15cb41abc7e5b909fea9b410d857c88a2
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 12 deletions.
@@ -1597,6 +1597,37 @@ Returns the `string` after replacing any surrogate code points
(or equivalently, any unpaired surrogate code units) with the
Unicode "replacement character" U+FFFD.

## `util.transferableAbortController()`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
Creates and returns an {AbortController} instance whose {AbortSignal} is marked
as transferable and can be used with `structuredClone()` or `postMessage()`.

## `util.transferableAbortSignal(signal)`

<!-- YAML
added: REPLACEME
-->

> Stability: 1 - Experimental
* `signal` {AbortSignal}
* Returns: {AbortSignal}

Marks the given {AbortSignal} as transferable so that it can be used with
`structuredClone()` and `postMessage()`.

```js
const signal = transferableAbortSignal(AbortSignal.timeout(100));
const channel = new MessageChannel();
channel.port2.postMessage(signal, [signal]);
```

## `util.types`

<!-- YAML
@@ -26,11 +26,13 @@ const {
const {
customInspectSymbol,
kEnumerableProperty,
kEmptyObject,
} = require('internal/util');
const { inspect } = require('internal/util/inspect');
const {
codes: {
ERR_ILLEGAL_CONSTRUCTOR,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_THIS,
}
} = require('internal/errors');
@@ -79,6 +81,7 @@ const kAborted = Symbol('kAborted');
const kReason = Symbol('kReason');
const kCloneData = Symbol('kCloneData');
const kTimeout = Symbol('kTimeout');
const kMakeTransferable = Symbol('kMakeTransferable');

function customInspect(self, obj, depth, options) {
if (depth < 0)
@@ -159,7 +162,7 @@ class AbortSignal extends EventTarget {
*/
static abort(
reason = new DOMException('This operation was aborted', 'AbortError')) {
return createAbortSignal(true, reason);
return createAbortSignal({ aborted: true, reason });
}

/**
@@ -179,10 +182,10 @@ class AbortSignal extends EventTarget {
[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) {
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
@@ -256,9 +259,9 @@ class AbortSignal extends EventTarget {
}

function ClonedAbortSignal() {
return createAbortSignal();
return createAbortSignal({ transferable: true });
}
ClonedAbortSignal.prototype[kDeserialize] = () => {};
ClonedAbortSignal.prototype[kDeserialize] = () => { };

ObjectDefineProperties(AbortSignal.prototype, {
aborted: kEnumerableProperty,
@@ -274,12 +277,25 @@ ObjectDefineProperty(AbortSignal.prototype, SymbolToStringTag, {

defineEventHandler(AbortSignal.prototype, 'abort');

function createAbortSignal(aborted = false, reason = undefined) {
/**
* @param {{
* aborted? : boolean,
* reason? : any,
* transferable? : boolean
* }} [init]
* @returns {AbortSignal}
*/
function createAbortSignal(init = kEmptyObject) {
const {
aborted = false,
reason = undefined,
transferable = false,
} = init;
const signal = new EventTarget();
ObjectSetPrototypeOf(signal, AbortSignal.prototype);
signal[kAborted] = aborted;
signal[kReason] = reason;
return lazyMakeTransferable(signal);
return transferable ? lazyMakeTransferable(signal) : signal;
}

function abortSignal(signal, reason) {
@@ -327,6 +343,30 @@ class AbortController {
signal: this.signal
}, depth, options);
}

static [kMakeTransferable]() {
const controller = new AbortController();
controller[kSignal] = transferableAbortSignal(controller[kSignal]);
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]();
}

ObjectDefineProperties(AbortController.prototype, {
@@ -347,4 +387,6 @@ module.exports = {
AbortController,
AbortSignal,
ClonedAbortSignal,
transferableAbortSignal,
transferableAbortController,
};
@@ -40,6 +40,8 @@ function setup() {
}

function makeTransferable(obj) {
// If the object is already transferable, skip all this.
if (obj instanceof JSTransferable) return obj;
const inst = ReflectConstruct(JSTransferable, [], obj.constructor);
const properties = ObjectGetOwnPropertyDescriptors(obj);
const propertiesValues = ObjectValues(properties);
@@ -79,6 +79,13 @@ const {
toUSVString,
} = require('internal/util');

let abortController;

function lazyAbortController() {
abortController ??= require('internal/abort_controller');
return abortController;
}

let internalDeepEqual;

/**
@@ -384,5 +391,11 @@ module.exports = {
toUSVString,
TextDecoder,
TextEncoder,
get transferableAbortSignal() {
return lazyAbortController().transferableAbortSignal;
},
get transferableAbortController() {
return lazyAbortController().transferableAbortController;
},
types
};
@@ -3,6 +3,11 @@
const common = require('../common');
const { ok, strictEqual } = require('assert');
const { setImmediate: pause } = require('timers/promises');
const {
transferableAbortSignal,
transferableAbortController,
} = require('util');


function deferred() {
let res;
@@ -11,7 +16,7 @@ function deferred() {
}

(async () => {
const ac = new AbortController();
const ac = transferableAbortController();
const mc = new MessageChannel();

const deferred1 = deferred();
@@ -54,7 +59,7 @@ function deferred() {
})().then(common.mustCall());

{
const signal = AbortSignal.abort('boom');
const signal = transferableAbortSignal(AbortSignal.abort('boom'));
ok(signal.aborted);
strictEqual(signal.reason, 'boom');
const mc = new MessageChannel();
@@ -70,7 +75,7 @@ function deferred() {
{
// The cloned AbortSignal does not keep the event loop open
// waiting for the abort to be triggered.
const ac = new AbortController();
const ac = transferableAbortController();
const mc = new MessageChannel();
mc.port1.onmessage = common.mustCall();
mc.port2.postMessage(ac.signal, [ac.signal]);

0 comments on commit 3f20e5b

Please sign in to comment.