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');