From 88289f592e0675b558d6a5f1e180583b6336de0b Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 20 Sep 2025 10:26:49 -0700 Subject: [PATCH] Revert "Don't remove existing unhandled exception and promise rejection handlers in Node" This reverts commit 6da88ec19eea2780c030dc95fcc55d059fed69e5. Removing existing handlers turns out to be load-bearing for Jasmine's parallel mode. ParallelWorker (in the jasmine package) installs a pair of handlers before booting core so that it can catch late async errors that happen after one spec file has finished executing and before the next starts. If those aren't uninstalled, errors that get routed through jasmine-core's normal error handling mechanism will also be reported via ParallelWorker's handlers. It might be possible for ParallelWorker to uninstall and install its handlers at the right time, but it's likely that there would be gaps in between when core uninstalls its handlers and when ParallelWorker installs. And in any case, the old behavior of GlobalErrors was a perfect match for what ParallelWorker needs, so let's keep it. --- lib/jasmine-core/jasmine.js | 44 ++++++++++++++++++++++++++--------- spec/core/GlobalErrorsSpec.js | 12 +++++++--- src/core/GlobalErrors.js | 44 ++++++++++++++++++++++++++--------- 3 files changed, 75 insertions(+), 25 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 94b74490..37135702 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -4559,34 +4559,56 @@ getJasmineRequireObj().GlobalErrors = function(j$) { class NodeAdapter { #global; #dispatch; + #originalHandlers; + #jasmineHandlers; constructor(global, dispatch) { this.#global = global; this.#dispatch = dispatch; + this.#jasmineHandlers = {}; + this.#originalHandlers = {}; + this.onError = this.onError.bind(this); this.onUnhandledRejection = this.onUnhandledRejection.bind(this); } install() { - this.#global.process.on('uncaughtException', this.onError); - this.#global.process.on('unhandledRejection', this.onUnhandledRejection); - this.#global.process.on( + this.#installHandler('uncaughtException', this.onError); + this.#installHandler('unhandledRejection', this.onUnhandledRejection); + this.#installHandler( 'rejectionHandled', this.#dispatch.onRejectionHandled ); } uninstall() { - this.#global.process.removeListener('uncaughtException', this.onError); - this.#global.process.removeListener( - 'unhandledRejection', - this.onUnhandledRejection - ); - this.#global.process.removeListener( - 'rejectionHandled', - this.#dispatch.onRejectionHandled + const errorTypes = Object.keys(this.#originalHandlers); + for (const errorType of errorTypes) { + this.#global.process.removeListener( + errorType, + this.#jasmineHandlers[errorType] + ); + + for (let i = 0; i < this.#originalHandlers[errorType].length; i++) { + this.#global.process.on( + errorType, + this.#originalHandlers[errorType][i] + ); + } + delete this.#originalHandlers[errorType]; + delete this.#jasmineHandlers[errorType]; + } + } + + #installHandler(errorType, handler) { + this.#originalHandlers[errorType] = this.#global.process.listeners( + errorType ); + this.#jasmineHandlers[errorType] = handler; + + this.#global.process.removeAllListeners(errorType); + this.#global.process.on(errorType, handler); } #augmentError(error, isUnhandledRejection) { diff --git a/spec/core/GlobalErrorsSpec.js b/spec/core/GlobalErrorsSpec.js index 799814f9..959bb340 100644 --- a/spec/core/GlobalErrorsSpec.js +++ b/spec/core/GlobalErrorsSpec.js @@ -142,9 +142,11 @@ describe('GlobalErrors', function() { errors.install(); expect(globals.listeners.uncaughtException).toEqual([ - originalHandler, jasmine.any(Function) ]); + expect(globals.listeners.uncaughtException).not.toEqual([ + originalHandler() + ]); errors.pushListener(handler); @@ -173,9 +175,11 @@ describe('GlobalErrors', function() { errors.install(); expect(globals.listeners.unhandledRejection).toEqual([ - originalHandler, jasmine.any(Function) ]); + expect(globals.listeners.unhandledRejection).not.toEqual([ + originalHandler() + ]); errors.pushListener(handler); @@ -253,9 +257,11 @@ describe('GlobalErrors', function() { errors.install(); expect(globals.listeners.rejectionHandled).toEqual([ - originalHandler, jasmine.any(Function) ]); + expect(globals.listeners.rejectionHandled).not.toEqual([ + originalHandler + ]); errors.uninstall(); expect(globals.listeners.rejectionHandled).toEqual([originalHandler]); diff --git a/src/core/GlobalErrors.js b/src/core/GlobalErrors.js index 7fd5d3b1..27af0128 100644 --- a/src/core/GlobalErrors.js +++ b/src/core/GlobalErrors.js @@ -191,34 +191,56 @@ getJasmineRequireObj().GlobalErrors = function(j$) { class NodeAdapter { #global; #dispatch; + #originalHandlers; + #jasmineHandlers; constructor(global, dispatch) { this.#global = global; this.#dispatch = dispatch; + this.#jasmineHandlers = {}; + this.#originalHandlers = {}; + this.onError = this.onError.bind(this); this.onUnhandledRejection = this.onUnhandledRejection.bind(this); } install() { - this.#global.process.on('uncaughtException', this.onError); - this.#global.process.on('unhandledRejection', this.onUnhandledRejection); - this.#global.process.on( + this.#installHandler('uncaughtException', this.onError); + this.#installHandler('unhandledRejection', this.onUnhandledRejection); + this.#installHandler( 'rejectionHandled', this.#dispatch.onRejectionHandled ); } uninstall() { - this.#global.process.removeListener('uncaughtException', this.onError); - this.#global.process.removeListener( - 'unhandledRejection', - this.onUnhandledRejection - ); - this.#global.process.removeListener( - 'rejectionHandled', - this.#dispatch.onRejectionHandled + const errorTypes = Object.keys(this.#originalHandlers); + for (const errorType of errorTypes) { + this.#global.process.removeListener( + errorType, + this.#jasmineHandlers[errorType] + ); + + for (let i = 0; i < this.#originalHandlers[errorType].length; i++) { + this.#global.process.on( + errorType, + this.#originalHandlers[errorType][i] + ); + } + delete this.#originalHandlers[errorType]; + delete this.#jasmineHandlers[errorType]; + } + } + + #installHandler(errorType, handler) { + this.#originalHandlers[errorType] = this.#global.process.listeners( + errorType ); + this.#jasmineHandlers[errorType] = handler; + + this.#global.process.removeAllListeners(errorType); + this.#global.process.on(errorType, handler); } #augmentError(error, isUnhandledRejection) {