From cef89815f8339abf5b9ee04a6bf57f406f585622 Mon Sep 17 00:00:00 2001 From: harjoth Date: Wed, 8 Jul 2026 20:14:36 -0700 Subject: [PATCH] inspector: load network tracking modules eagerly `Network.enable` is dispatched to the main thread through a V8 interrupt that can run in the middle of arbitrary JavaScript execution, including while another module is still being loaded by require(). The network tracking enable()/disable() helpers lazily require()d their modules, which pull in `inspector`, `worker_threads` and `stream`. When the interrupt landed mid-require(), require() returned a half-initialized module and threw (for example "Class extends value undefined" from internal/worker/io, or "require(...).enable is not a function"). The inspector agent treats any exception thrown while toggling network tracking as unrecoverable and aborts the whole process. Load the three network tracking modules eagerly at setup time so that enable()/disable() never call require() from an interrupt. They now run during setupNetworkInspection() at bootstrap, before the inspector can dispatch Network.enable. Fixes: https://github.com/nodejs/node/issues/64308 --- lib/internal/inspector_network_tracking.js | 25 ++++++++--- ...t-inspector-network-tracking-eager-load.js | 43 +++++++++++++++++++ 2 files changed, 62 insertions(+), 6 deletions(-) create mode 100644 test/parallel/test-inspector-network-tracking-eager-load.js diff --git a/lib/internal/inspector_network_tracking.js b/lib/internal/inspector_network_tracking.js index 4044edbddb5356..04719a6fef50a0 100644 --- a/lib/internal/inspector_network_tracking.js +++ b/lib/internal/inspector_network_tracking.js @@ -1,15 +1,28 @@ 'use strict'; +// The network tracking modules are loaded eagerly here, at setup time, rather +// than lazily inside enable()/disable(). enable() can be invoked from an +// inspector interrupt triggered by a `Network.enable` message, which lands in +// the middle of arbitrary JavaScript execution -- including while another +// module is still being loaded by require(). Loading these modules at that +// point (they pull in `inspector`, `worker_threads` and `stream`) can observe +// a half-initialized module and throw, which the inspector agent turns into a +// fatal, unrecoverable error that aborts the process. +// Refs: https://github.com/nodejs/node/issues/64308 +const networkHttp = require('internal/inspector/network_http'); +const networkHttp2 = require('internal/inspector/network_http2'); +const networkUndici = require('internal/inspector/network_undici'); + function enable() { - require('internal/inspector/network_http').enable(); - require('internal/inspector/network_http2').enable(); - require('internal/inspector/network_undici').enable(); + networkHttp.enable(); + networkHttp2.enable(); + networkUndici.enable(); } function disable() { - require('internal/inspector/network_http').disable(); - require('internal/inspector/network_http2').disable(); - require('internal/inspector/network_undici').disable(); + networkHttp.disable(); + networkHttp2.disable(); + networkUndici.disable(); } module.exports = { diff --git a/test/parallel/test-inspector-network-tracking-eager-load.js b/test/parallel/test-inspector-network-tracking-eager-load.js new file mode 100644 index 00000000000000..c9a715e6106d99 --- /dev/null +++ b/test/parallel/test-inspector-network-tracking-eager-load.js @@ -0,0 +1,43 @@ +// Flags: --expose-internals --inspect=0 --experimental-network-inspection +'use strict'; + +// Regression test for https://github.com/nodejs/node/issues/64308. +// +// A `Network.enable` inspector message is dispatched to the main thread via an +// interrupt that can land in the middle of arbitrary JavaScript execution, +// including while another module is still being loaded by require(). If the +// network tracking enable() path lazily require()s its modules (which pull in +// `inspector`, `worker_threads` and `stream`) at that point, it can observe a +// half-initialized module and throw. The inspector agent treats any exception +// thrown while toggling network tracking as an unrecoverable fatal error and +// aborts the whole process. +// +// Guard against a regression by asserting that the network tracking modules +// are loaded eagerly at setup time and that enable()/disable() do not load any +// module themselves. + +const common = require('../common'); +common.skipIfInspectorDisabled(); + +const assert = require('assert'); +const { enable, disable } = require('internal/inspector_network_tracking'); + +// With --experimental-network-inspection, the tracking modules must already be +// loaded (they are required at module scope of internal/inspector_network_tracking, +// which itself is loaded during bootstrap when the flag is set). +for (const mod of [ + 'NativeModule internal/inspector/network_http', + 'NativeModule internal/inspector/network_http2', + 'NativeModule internal/inspector/network_undici', +]) { + assert(process.moduleLoadList.includes(mod), + `${mod} should be loaded eagerly at setup, but was not`); +} + +// enable()/disable() must not trigger any further module loading, so that they +// are safe to run from an inspector interrupt that fires mid-require(). +const before = process.moduleLoadList.length; +enable(); +disable(); +assert.strictEqual(process.moduleLoadList.length, before, + 'enable()/disable() must not lazily load any module');