From 832efd8e30619738ac9e2ebf7d3358c428aa5736 Mon Sep 17 00:00:00 2001 From: Debadree Chatterjee Date: Sun, 7 May 2023 23:54:58 +0530 Subject: [PATCH] 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); }