From 6e797f53aa20055b9a8125d886f21319a50810fc Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Sat, 4 Feb 2023 16:03:44 +0530 Subject: [PATCH 01/26] lib: add aborted() utility function Fixes: https://github.com/nodejs/node/issues/37220 Refs: https://github.com/nodejs/node/pull/36607 --- lib/internal/abort_controller.js | 18 +++++++++++++++ lib/util.js | 3 +++ test/parallel/test-aborted-util.js | 37 ++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 test/parallel/test-aborted-util.js diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 7a4381ecd13cb3..82d86f9c59f8fa 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -13,6 +13,7 @@ const { Symbol, SymbolToStringTag, WeakRef, + PromiseResolve, } = primordials; const { @@ -22,11 +23,13 @@ const { kTrustEvent, kNewListener, kRemoveListener, + kWeakHandler, } = require('internal/event_target'); const { customInspectSymbol, kEnumerableProperty, kEmptyObject, + createDeferredPromise, } = require('internal/util'); const { inspect } = require('internal/util/inspect'); const { @@ -357,6 +360,20 @@ function transferableAbortController() { return AbortController[kMakeTransferable](); } +/** + * @param {AbortSignal} signal + * @param {any} resource + * @returns {Promise} + */ +function aborted(signal, resource = null) { + validateAbortSignal(signal, 'signal'); + if (signal.aborted) + return PromiseResolve(); + const abortPromise = createDeferredPromise(); + signal.addEventListener('abort', abortPromise.resolve, { [kWeakHandler]: resource, once: true }); + return abortPromise.promise; +} + ObjectDefineProperties(AbortController.prototype, { signal: kEnumerableProperty, abort: kEnumerableProperty, @@ -377,4 +394,5 @@ module.exports = { ClonedAbortSignal, transferableAbortSignal, transferableAbortController, + aborted, }; diff --git a/lib/util.js b/lib/util.js index 893a085c90aa3b..4ee13a28f58512 100644 --- a/lib/util.js +++ b/lib/util.js @@ -393,6 +393,9 @@ module.exports = { get transferableAbortController() { return lazyAbortController().transferableAbortController; }, + get aborted() { + return lazyAbortController().aborted; + }, types }; diff --git a/test/parallel/test-aborted-util.js b/test/parallel/test-aborted-util.js new file mode 100644 index 00000000000000..563d9ad922fc71 --- /dev/null +++ b/test/parallel/test-aborted-util.js @@ -0,0 +1,37 @@ +// Flags: --expose-gc +'use strict'; + +const common = require('../common'); +const { aborted } = require('util'); +const assert = require('assert'); +const { getEventListeners } = require('events'); + +{ + // Test aborted works + const ac = new AbortController(); + aborted(ac.signal).then(common.mustCall()); + ac.abort(); + assert.strictEqual(ac.signal.aborted, true); + assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0); +} + +{ + // Test aborted works when provided a resource + const ac = new AbortController(); + aborted(ac.signal, {}).then(common.mustCall()); + ac.abort(); + assert.strictEqual(ac.signal.aborted, true); + assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0); +} + +{ + // Test aborted with gc cleanup + const ac = new AbortController(); + aborted(ac.signal, {}).then(common.mustNotCall()); + setImmediate(() => { + global.gc(); + ac.abort(); + assert.strictEqual(ac.signal.aborted, true); + assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0); + }); +} From 3834e9f3e9a948c40087d7f19c63ca59f442688b Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Sat, 4 Feb 2023 16:33:30 +0530 Subject: [PATCH 02/26] fixup! update param validation --- lib/internal/abort_controller.js | 8 +++++++- test/parallel/test-aborted-util.js | 8 ++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 82d86f9c59f8fa..35e9825ec9626a 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -14,6 +14,7 @@ const { SymbolToStringTag, WeakRef, PromiseResolve, + PromiseReject, } = primordials; const { @@ -42,6 +43,7 @@ const { const { validateUint32, + validateAbortSignal: abortSignalValidator, } = require('internal/validators'); const { @@ -366,7 +368,11 @@ function transferableAbortController() { * @returns {Promise} */ function aborted(signal, resource = null) { - validateAbortSignal(signal, 'signal'); + try { + abortSignalValidator(signal, 'signal'); + } catch (err) { + return PromiseReject(err); + } if (signal.aborted) return PromiseResolve(); const abortPromise = createDeferredPromise(); diff --git a/test/parallel/test-aborted-util.js b/test/parallel/test-aborted-util.js index 563d9ad922fc71..bf910a0a6a8e45 100644 --- a/test/parallel/test-aborted-util.js +++ b/test/parallel/test-aborted-util.js @@ -35,3 +35,11 @@ const { getEventListeners } = require('events'); assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0); }); } + +{ + // Fails with error if not provided abort signal + const sig = new EventTarget(); + assert.rejects(aborted(sig), { + name: 'TypeError', + }); +} From 1cab0ca4571128ba0808bf6dee433c703f2e724a Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Sat, 4 Feb 2023 23:47:48 +0530 Subject: [PATCH 03/26] fixup! make async func --- lib/internal/abort_controller.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 35e9825ec9626a..f2ccb9f47f6774 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -14,7 +14,6 @@ const { SymbolToStringTag, WeakRef, PromiseResolve, - PromiseReject, } = primordials; const { @@ -367,12 +366,8 @@ function transferableAbortController() { * @param {any} resource * @returns {Promise} */ -function aborted(signal, resource = null) { - try { - abortSignalValidator(signal, 'signal'); - } catch (err) { - return PromiseReject(err); - } +async function aborted(signal, resource = null) { + abortSignalValidator(signal, 'signal'); if (signal.aborted) return PromiseResolve(); const abortPromise = createDeferredPromise(); From 2e41a14fbf2fb63bd9c580374b2f539ca9aca0c0 Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Sun, 5 Feb 2023 00:06:52 +0530 Subject: [PATCH 04/26] fixup! make resource compulsory --- lib/internal/abort_controller.js | 4 +++- test/parallel/test-aborted-util.js | 19 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index f2ccb9f47f6774..897da06efecd46 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -43,6 +43,7 @@ const { const { validateUint32, validateAbortSignal: abortSignalValidator, + validateObject, } = require('internal/validators'); const { @@ -366,8 +367,9 @@ function transferableAbortController() { * @param {any} resource * @returns {Promise} */ -async function aborted(signal, resource = null) { +async function aborted(signal, resource) { abortSignalValidator(signal, 'signal'); + validateObject(resource, 'resource', { nullable: false, allowFunction: true, allowArray: true }); if (signal.aborted) return PromiseResolve(); const abortPromise = createDeferredPromise(); diff --git a/test/parallel/test-aborted-util.js b/test/parallel/test-aborted-util.js index bf910a0a6a8e45..e854079222a452 100644 --- a/test/parallel/test-aborted-util.js +++ b/test/parallel/test-aborted-util.js @@ -6,15 +6,6 @@ const { aborted } = require('util'); const assert = require('assert'); const { getEventListeners } = require('events'); -{ - // Test aborted works - const ac = new AbortController(); - aborted(ac.signal).then(common.mustCall()); - ac.abort(); - assert.strictEqual(ac.signal.aborted, true); - assert.strictEqual(getEventListeners(ac.signal, 'abort').length, 0); -} - { // Test aborted works when provided a resource const ac = new AbortController(); @@ -39,7 +30,15 @@ const { getEventListeners } = require('events'); { // Fails with error if not provided abort signal const sig = new EventTarget(); - assert.rejects(aborted(sig), { + assert.rejects(aborted(sig, {}), { + name: 'TypeError', + }); +} + +{ + // Fails if not provided a resource + const ac = new AbortController(); + assert.rejects(aborted(ac.signal, null), { name: 'TypeError', }); } From 0a58ffc057cdbecc7965092e95d829d54110ebe1 Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Sun, 5 Feb 2023 00:25:10 +0530 Subject: [PATCH 05/26] fixup! add doc --- doc/api/util.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/doc/api/util.md b/doc/api/util.md index 49e4d231d05179..99d3867b8396dd 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -1989,6 +1989,35 @@ const channel = new MessageChannel(); channel.port2.postMessage(signal, [signal]); ``` +## `util.aborted(signal, resource)` + + + +> Stability: 1 - Experimental + +* `signal` {AbortSignal} +* `resource` {any} Any non-null entity +* Returns: {Promise} + +Listens to abort event on the provided `signal` and returns a promise which is +resolved if an abort event is triggered. The function cleans up any +event listeners depending on when the provided `resource` is garbage collected. + +```js +const { aborted } = require('util'); + +const ac = new AbortController(); +const dependent = {}; + +aborted(ac.signal, dependent).then(() => { + // Do something when the abort event is triggered. +}); + +doSomething(dependent, ac.signal); // Can trigger the abort event +``` + ## `util.types`