Skip to content

Commit 6ff3b36

Browse files
flakey5jasnell
authored andcommitted
lib: refactor transferable AbortSignal
Co-authored-by: James M Snell <[email protected]> PR-URL: nodejs#44048 Reviewed-By: Matteo Collina <[email protected]> Reviewed-By: Stephen Belanger <[email protected]> Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Joyee Cheung <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent e35849b commit 6ff3b36

File tree

5 files changed

+100
-7
lines changed

5 files changed

+100
-7
lines changed

doc/api/util.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1593,6 +1593,37 @@ Returns the `string` after replacing any surrogate code points
15931593
(or equivalently, any unpaired surrogate code units) with the
15941594
Unicode "replacement character" U+FFFD.
15951595
1596+
## `util.transferableAbortController()`
1597+
1598+
<!-- YAML
1599+
added: REPLACEME
1600+
-->
1601+
1602+
> Stability: 1 - Experimental
1603+
1604+
Creates and returns an {AbortController} instance whose {AbortSignal} is marked
1605+
as transferable and can be used with `structuredClone()` or `postMessage()`.
1606+
1607+
## `util.transferableAbortSignal(signal)`
1608+
1609+
<!-- YAML
1610+
added: REPLACEME
1611+
-->
1612+
1613+
> Stability: 1 - Experimental
1614+
1615+
* `signal` {AbortSignal}
1616+
* Returns: {AbortSignal}
1617+
1618+
Marks the given {AbortSignal} as transferable so that it can be used with
1619+
`structuredClone()` and `postMessage()`.
1620+
1621+
```js
1622+
const signal = transferableAbortSignal(AbortSignal.timeout(100));
1623+
const channel = new MessageChannel();
1624+
channel.port2.postMessage(signal, [signal]);
1625+
```
1626+
15961627
## `util.types`
15971628
15981629
<!-- YAML

lib/internal/abort_controller.js

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,13 @@ const {
2626
const {
2727
customInspectSymbol,
2828
kEnumerableProperty,
29+
kEmptyObject,
2930
} = require('internal/util');
3031
const { inspect } = require('internal/util/inspect');
3132
const {
3233
codes: {
3334
ERR_ILLEGAL_CONSTRUCTOR,
35+
ERR_INVALID_ARG_TYPE,
3436
ERR_INVALID_THIS,
3537
}
3638
} = require('internal/errors');
@@ -79,6 +81,7 @@ const kAborted = Symbol('kAborted');
7981
const kReason = Symbol('kReason');
8082
const kCloneData = Symbol('kCloneData');
8183
const kTimeout = Symbol('kTimeout');
84+
const kMakeTransferable = Symbol('kMakeTransferable');
8285

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

165168
/**
@@ -256,7 +259,7 @@ class AbortSignal extends EventTarget {
256259
}
257260

258261
function ClonedAbortSignal() {
259-
return createAbortSignal();
262+
return createAbortSignal({ transferable: true });
260263
}
261264
ClonedAbortSignal.prototype[kDeserialize] = () => {};
262265

@@ -274,12 +277,25 @@ ObjectDefineProperty(AbortSignal.prototype, SymbolToStringTag, {
274277

275278
defineEventHandler(AbortSignal.prototype, 'abort');
276279

277-
function createAbortSignal(aborted = false, reason = undefined) {
280+
/**
281+
* @param {{
282+
* aborted? : boolean,
283+
* reason? : any,
284+
* transferable? : boolean
285+
* }} [init]
286+
* @returns {AbortSignal}
287+
*/
288+
function createAbortSignal(init = kEmptyObject) {
289+
const {
290+
aborted = false,
291+
reason = undefined,
292+
transferable = false,
293+
} = init;
278294
const signal = new EventTarget();
279295
ObjectSetPrototypeOf(signal, AbortSignal.prototype);
280296
signal[kAborted] = aborted;
281297
signal[kReason] = reason;
282-
return lazyMakeTransferable(signal);
298+
return transferable ? lazyMakeTransferable(signal) : signal;
283299
}
284300

285301
function abortSignal(signal, reason) {
@@ -314,6 +330,30 @@ class AbortController {
314330
signal: this.signal
315331
}, depth, options);
316332
}
333+
334+
static [kMakeTransferable]() {
335+
const controller = new AbortController();
336+
controller.#signal = transferableAbortSignal(controller.#signal);
337+
return controller;
338+
}
339+
}
340+
341+
/**
342+
* Enables the AbortSignal to be transferable using structuredClone/postMessage.
343+
* @param {AbortSignal} signal
344+
* @returns {AbortSignal}
345+
*/
346+
function transferableAbortSignal(signal) {
347+
if (signal?.[kAborted] === undefined)
348+
throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
349+
return lazyMakeTransferable(signal);
350+
}
351+
352+
/**
353+
* Creates an AbortController with a transferable AbortSignal
354+
*/
355+
function transferableAbortController() {
356+
return AbortController[kMakeTransferable]();
317357
}
318358

319359
ObjectDefineProperties(AbortController.prototype, {
@@ -334,4 +374,6 @@ module.exports = {
334374
AbortController,
335375
AbortSignal,
336376
ClonedAbortSignal,
377+
transferableAbortSignal,
378+
transferableAbortController,
337379
};

lib/internal/worker/js_transferable.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ function setup() {
4040
}
4141

4242
function makeTransferable(obj) {
43+
// If the object is already transferable, skip all this.
44+
if (obj instanceof JSTransferable) return obj;
4345
const inst = ReflectConstruct(JSTransferable, [], obj.constructor);
4446
const properties = ObjectGetOwnPropertyDescriptors(obj);
4547
const propertiesValues = ObjectValues(properties);

lib/util.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ const {
7878
toUSVString,
7979
} = require('internal/util');
8080

81+
let abortController;
82+
83+
function lazyAbortController() {
84+
abortController ??= require('internal/abort_controller');
85+
return abortController;
86+
}
87+
8188
let internalDeepEqual;
8289

8390
/**
@@ -377,5 +384,11 @@ module.exports = {
377384
toUSVString,
378385
TextDecoder,
379386
TextEncoder,
387+
get transferableAbortSignal() {
388+
return lazyAbortController().transferableAbortSignal;
389+
},
390+
get transferableAbortController() {
391+
return lazyAbortController().transferableAbortController;
392+
},
380393
types
381394
};

test/parallel/test-abortsignal-cloneable.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@
33
const common = require('../common');
44
const { ok, strictEqual } = require('assert');
55
const { setImmediate: pause } = require('timers/promises');
6+
const {
7+
transferableAbortSignal,
8+
transferableAbortController,
9+
} = require('util');
10+
611

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

1318
(async () => {
14-
const ac = new AbortController();
19+
const ac = transferableAbortController();
1520
const mc = new MessageChannel();
1621

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

5661
{
57-
const signal = AbortSignal.abort('boom');
62+
const signal = transferableAbortSignal(AbortSignal.abort('boom'));
5863
ok(signal.aborted);
5964
strictEqual(signal.reason, 'boom');
6065
const mc = new MessageChannel();
@@ -70,7 +75,7 @@ function deferred() {
7075
{
7176
// The cloned AbortSignal does not keep the event loop open
7277
// waiting for the abort to be triggered.
73-
const ac = new AbortController();
78+
const ac = transferableAbortController();
7479
const mc = new MessageChannel();
7580
mc.port1.onmessage = common.mustCall();
7681
mc.port2.postMessage(ac.signal, [ac.signal]);

0 commit comments

Comments
 (0)