From 832efd8e30619738ac9e2ebf7d3358c428aa5736 Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Sun, 7 May 2023 23:54:58 +0530 Subject: [PATCH 1/2] lib: reuse default DOMException in AbortController Refs: https://github.com/nodejs/node/pull/46086 Refs: https://github.com/nodejs/performance/issues/44 --- benchmark/events/abortcontroller-abort.js | 16 ++++++++++++ lib/internal/abort_controller.js | 30 ++++++++++++++++++++--- 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 benchmark/events/abortcontroller-abort.js diff --git a/benchmark/events/abortcontroller-abort.js b/benchmark/events/abortcontroller-abort.js new file mode 100644 index 00000000000000..c3a675be10173b --- /dev/null +++ b/benchmark/events/abortcontroller-abort.js @@ -0,0 +1,16 @@ +'use strict'; +const common = require('../common.js'); + +const bench = common.createBenchmark(main, { + n: [1e6], +}); + +function main({ n }) { + bench.start(); + for (let i = 0; i < n; i++) { + const ac = new AbortController(); + ac.signal.addEventListener('abort', () => {}); + ac.abort(); + } + bench.end(n); +} diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index 2c1f43354f9f7c..b0d9074a050da9 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -30,6 +30,7 @@ const { customInspectSymbol, kEmptyObject, kEnumerableProperty, + SideEffectFreeRegExpPrototypeSymbolReplace, } = require('internal/util'); const { inspect } = require('internal/util/inspect'); const { @@ -63,11 +64,14 @@ const { let _MessageChannel; let makeTransferable; +let defaultDOMException; // 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. +const userModuleRegExp = /^ {4}at (?:[^/\\(]+ \()(?!node:(.+):\d+:\d+\)$).*/gm; + function lazyMessageChannel() { _MessageChannel ??= require('internal/worker/io').MessageChannel; return new _MessageChannel(); @@ -79,6 +83,21 @@ function lazyMakeTransferable(obj) { return makeTransferable(obj); } +function lazyDOMException() { + if (defaultDOMException) { + return defaultDOMException; + } + + defaultDOMException = new DOMException('This operation was aborted', 'AbortError'); + + // Avoid V8 leak and remove userland stackstrace + defaultDOMException.stack = SideEffectFreeRegExpPrototypeSymbolReplace( + userModuleRegExp, + defaultDOMException.stack, + ''); + return defaultDOMException; +} + const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout); const timeOutSignals = new SafeSet(); @@ -166,8 +185,10 @@ class AbortSignal extends EventTarget { * @param {any} [reason] * @returns {AbortSignal} */ - static abort( - reason = new DOMException('This operation was aborted', 'AbortError')) { + static abort(reason) { + if (reason === undefined) { + reason = lazyDOMException(); + } return createAbortSignal({ aborted: true, reason }); } @@ -328,7 +349,10 @@ class AbortController { /** * @param {any} [reason] */ - abort(reason = new DOMException('This operation was aborted', 'AbortError')) { + abort(reason) { + if (reason === undefined) { + reason = lazyDOMException(); + } abortSignal(this.#signal ??= createAbortSignal(), reason); } From 5259cbe3b7c3d5f4602e369ca80727e649c94962 Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Mon, 8 May 2023 11:21:35 +0530 Subject: [PATCH 2/2] fixup! use stack trace --- lib/internal/abort_controller.js | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/internal/abort_controller.js b/lib/internal/abort_controller.js index b0d9074a050da9..e4c0633c5ef945 100644 --- a/lib/internal/abort_controller.js +++ b/lib/internal/abort_controller.js @@ -4,6 +4,7 @@ // in https://github.com/mysticatea/abort-controller (MIT license) const { + ErrorCaptureStackTrace, ObjectAssign, ObjectDefineProperties, ObjectSetPrototypeOf, @@ -30,7 +31,6 @@ const { customInspectSymbol, kEmptyObject, kEnumerableProperty, - SideEffectFreeRegExpPrototypeSymbolReplace, } = require('internal/util'); const { inspect } = require('internal/util/inspect'); const { @@ -70,8 +70,6 @@ let defaultDOMException; // because otherwise we'll end up with a require cycle that ends up with // an incomplete initialization of abort_controller. -const userModuleRegExp = /^ {4}at (?:[^/\\(]+ \()(?!node:(.+):\d+:\d+\)$).*/gm; - function lazyMessageChannel() { _MessageChannel ??= require('internal/worker/io').MessageChannel; return new _MessageChannel(); @@ -90,11 +88,9 @@ function lazyDOMException() { defaultDOMException = new DOMException('This operation was aborted', 'AbortError'); - // Avoid V8 leak and remove userland stackstrace - defaultDOMException.stack = SideEffectFreeRegExpPrototypeSymbolReplace( - userModuleRegExp, - defaultDOMException.stack, - ''); + // Avoid V8 leak + // eslint-disable-next-line no-unused-vars + const dummy = defaultDOMException.stack; return defaultDOMException; } @@ -185,9 +181,10 @@ class AbortSignal extends EventTarget { * @param {any} [reason] * @returns {AbortSignal} */ - static abort(reason) { + static abort(reason = undefined) { if (reason === undefined) { reason = lazyDOMException(); + ErrorCaptureStackTrace(reason); } return createAbortSignal({ aborted: true, reason }); } @@ -352,6 +349,7 @@ class AbortController { abort(reason) { if (reason === undefined) { reason = lazyDOMException(); + ErrorCaptureStackTrace(reason); } abortSignal(this.#signal ??= createAbortSignal(), reason); }