From e0a02676460c9dd85f0bfb0c976a17f9557ea5bf Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 29 Apr 2026 21:40:35 +0200 Subject: [PATCH 1/2] test: ignore Node http socketErrorListener leak MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This stops `MaxListenersExceededWarning` from crashing the test process on Node v24.15.0, which leaks an internal `socketErrorListener` on every keep-alive agent socket reuse. Tests that issue several requests through the same socket — `instrumentation-http` and the RASP fastify blocking suite are the current victims — pile up enough listeners to trip our strict `defaultMaxListeners=6` and crash via the warning rethrow. Detect the leak by its stable signature: two or more listeners named `socketErrorListener` on the same emitter for event `error`. The upstream fix is merged on Node `main` but not yet released in v24.x; once it ships the duplicates disappear and the detector returns false again, so real listener leaks resume throwing. Refs: https://github.com/nodejs/node/pull/61770 Refs: https://github.com/nodejs/node/pull/62872 --- packages/dd-trace/test/setup/core.js | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/packages/dd-trace/test/setup/core.js b/packages/dd-trace/test/setup/core.js index 86b26a9b19..d853855e78 100644 --- a/packages/dd-trace/test/setup/core.js +++ b/packages/dd-trace/test/setup/core.js @@ -66,6 +66,24 @@ temporaryWarningExceptions.add = (warning) => { return originalAdd(warning) } +// Suppress Node's HTTP keep-alive `socketErrorListener` leak (introduced by +// https://github.com/nodejs/node/pull/61770; fix at +// https://github.com/nodejs/node/pull/62872 not yet released in v24.x). Bump +// the leaking socket's limit and drop the warning before stderr or the +// `'warning'` event sees it, so real leaks still throw below. +const originalEmitWarning = process.emitWarning +process.emitWarning = function patchedEmitWarning (warning, ...args) { + if ( + typeof warning === 'object' && + warning?.name === 'MaxListenersExceededWarning' && + isNodeHttpSocketLeak(warning) + ) { + warning.emitter.setMaxListeners(0) + return + } + return originalEmitWarning.call(this, warning, ...args) +} + process.on('warning', (warning) => { if (warning.name === 'MaxListenersExceededWarning') { throw warning @@ -84,6 +102,28 @@ process.on('warning', (warning) => { } }) +/** + * Detect the Node.js HTTP keep-alive socket error listener leak by its only + * stable signature: two or more listeners named `socketErrorListener` on the + * same emitter for event `error`. Once the upstream fix ships the duplicates + * disappear and this returns false again, so real leaks resume throwing. + * + * @param {Error & { emitter?: NodeJS.EventEmitter, type?: string }} warning + * @returns {boolean} + */ +function isNodeHttpSocketLeak (warning) { + if (warning.type !== 'error' || typeof warning.emitter?.listeners !== 'function') { + return false + } + let count = 0 + for (const listener of warning.emitter.listeners('error')) { + if (listener.name === 'socketErrorListener' && ++count > 1) { + return true + } + } + return false +} + // Make this file a module for type-aware tooling. It is intentionally imported // for side effects only. module.exports = { From caf535b50e5044626a956049d3603a826482ef37 Mon Sep 17 00:00:00 2001 From: Ruben Bridgewater Date: Wed, 29 Apr 2026 22:46:26 +0200 Subject: [PATCH 2/2] fixup! linter --- packages/dd-trace/test/setup/core.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/dd-trace/test/setup/core.js b/packages/dd-trace/test/setup/core.js index d853855e78..bce5a7dce4 100644 --- a/packages/dd-trace/test/setup/core.js +++ b/packages/dd-trace/test/setup/core.js @@ -73,11 +73,7 @@ temporaryWarningExceptions.add = (warning) => { // `'warning'` event sees it, so real leaks still throw below. const originalEmitWarning = process.emitWarning process.emitWarning = function patchedEmitWarning (warning, ...args) { - if ( - typeof warning === 'object' && - warning?.name === 'MaxListenersExceededWarning' && - isNodeHttpSocketLeak(warning) - ) { + if (warning?.name === 'MaxListenersExceededWarning' && isNodeHttpSocketLeak(warning)) { warning.emitter.setMaxListeners(0) return }