mirror of
https://github.com/nodejs/node.git
synced 2026-10-29 02:35:20 +08:00
Every `new Worker()` runs the whole internal bootstrap (realm, node, web exposure, thread and process-state switches) in its fresh isolate, compiling ~80 builtins with the code cache; only the main thread deserializes its principal context from the built-in snapshot. That bootstrap is about half of a worker's cold start. The bootstrapped principal context in the snapshot is nearly thread-neutral: the worker-side switch scripts (is_not_main_thread, does_not_own_process_state) are written as overrides of the main-thread ones, and the per-thread values of the `worker` binding were the only thread-specific data baked into the context. Let a worker deserialize that same context and EnvSerializeInfo and apply the two worker-side switches on top: - worker binding: threadId, threadName, isMainThread, isInternalThread, ownsProcessState and resourceLimits become lazy properties of the per-isolate template, computed from the Environment on first read. - CreateEnvironment(): when a worker (its IsolateData has a Worker) passes an empty context, deserialize kNodeMainContextIndex and run internal/bootstrap/switches/is_not_main_thread and, unless the worker owns process state, does_not_own_process_state after InitializeMainContext(). - Worker::Run(): take that path when the embedded built-in snapshot is in use (not an embedder's or a --snapshot-blob one, which has run application code), browser globals are not disabled and --no-worker-snapshot was not given; otherwise bootstrap as before. - is_not_main_thread.js: also delete _debugPause and the profiler idle notifier helpers that is_main_thread.js installs. - pre_execution: run the snapshot's deserialize callbacks (Buffer pool, default resolver, cached cwd) on worker threads too, now that a worker can come from a snapshot. - --[no-]worker-snapshot per-isolate option, documented. Sequential new Worker() -> 'online' -> terminate goes from ~20.9 ms to ~10.3 ms per worker on x64 Linux; --no-worker-snapshot restores the old number. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65336 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
277 lines
10 KiB
JavaScript
277 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
// This list must be computed before we require any builtins to
|
|
// to eliminate the noise.
|
|
const list = process.moduleLoadList.slice();
|
|
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const { inspect } = require('util');
|
|
|
|
const preExecIndex =
|
|
list.findIndex((i) => i.includes('pre_execution'));
|
|
const actual = {
|
|
beforePreExec: new Set(list.slice(0, preExecIndex)),
|
|
atRunTime: new Set(list.slice(preExecIndex)),
|
|
};
|
|
|
|
// Currently, we don't add additional builtins to worker snapshots.
|
|
// So for worker snapshots we'll just concatenate the two. Once we
|
|
// add more builtins to worker snapshots, we should also distinguish
|
|
// the two stages for them.
|
|
const expected = {};
|
|
const getFormatNativeModule = 'NativeModule internal/modules/esm/get_format';
|
|
|
|
expected.beforePreExec = new Set([
|
|
'Internal Binding builtins',
|
|
'Internal Binding modules',
|
|
'Internal Binding errors',
|
|
'Internal Binding util',
|
|
'NativeModule internal/errors',
|
|
'Internal Binding config',
|
|
'Internal Binding timers',
|
|
'Internal Binding async_context_frame',
|
|
'NativeModule internal/async_context_frame',
|
|
'Internal Binding async_wrap',
|
|
'Internal Binding task_queue',
|
|
'Internal Binding symbols',
|
|
'NativeModule internal/async_hooks',
|
|
'Internal Binding constants',
|
|
'Internal Binding types',
|
|
'NativeModule internal/util',
|
|
'NativeModule internal/util/types',
|
|
'NativeModule internal/validators',
|
|
'NativeModule internal/linkedlist',
|
|
'NativeModule internal/priority_queue',
|
|
'NativeModule internal/assert',
|
|
'NativeModule internal/util/inspect',
|
|
'NativeModule internal/util/debuglog',
|
|
'NativeModule internal/streams/utils',
|
|
'NativeModule internal/timers',
|
|
'NativeModule events',
|
|
'Internal Binding buffer',
|
|
'Internal Binding string_decoder',
|
|
'NativeModule util/types',
|
|
'NativeModule internal/buffer',
|
|
'NativeModule buffer',
|
|
'Internal Binding messaging',
|
|
'NativeModule internal/worker/js_transferable',
|
|
'Internal Binding process_methods',
|
|
'NativeModule internal/process/per_thread',
|
|
'Internal Binding credentials',
|
|
'NativeModule internal/process/promises',
|
|
'NativeModule internal/fixed_queue',
|
|
'NativeModule async_hooks',
|
|
'NativeModule internal/process/task_queues',
|
|
'NativeModule timers',
|
|
'Internal Binding trace_events',
|
|
'NativeModule internal/constants',
|
|
'NativeModule path',
|
|
'NativeModule internal/process/execution',
|
|
'NativeModule internal/process/permission',
|
|
'NativeModule internal/process/warning',
|
|
'NativeModule internal/console/constructor',
|
|
'NativeModule internal/console/global',
|
|
'NativeModule internal/querystring',
|
|
'NativeModule querystring',
|
|
'Internal Binding url',
|
|
'Internal Binding url_pattern',
|
|
'Internal Binding blob',
|
|
'NativeModule internal/url',
|
|
'NativeModule util',
|
|
'NativeModule internal/webidl',
|
|
'Internal Binding performance',
|
|
'Internal Binding permission',
|
|
'NativeModule internal/perf/utils',
|
|
'NativeModule internal/event_target',
|
|
'Internal Binding mksnapshot',
|
|
'NativeModule internal/v8/startup_snapshot',
|
|
'NativeModule internal/process/signal',
|
|
'Internal Binding fs',
|
|
'NativeModule internal/fs/utils',
|
|
'NativeModule fs',
|
|
'Internal Binding options',
|
|
'NativeModule internal/options',
|
|
'NativeModule internal/source_map/source_map_cache',
|
|
'Internal Binding contextify',
|
|
'NativeModule internal/vm',
|
|
'NativeModule internal/modules/helpers',
|
|
'NativeModule internal/modules/customization_hooks',
|
|
'NativeModule internal/modules/package_json_reader',
|
|
'Internal Binding module_wrap',
|
|
'NativeModule internal/modules/cjs/loader',
|
|
'NativeModule internal/modules/package_map',
|
|
'NativeModule diagnostics_channel',
|
|
'Internal Binding diagnostics_channel',
|
|
'Internal Binding wasm_web_api',
|
|
'NativeModule internal/events/abort_listener',
|
|
'NativeModule internal/modules/esm/utils',
|
|
'Internal Binding worker',
|
|
'NativeModule internal/modules/run_main',
|
|
getFormatNativeModule,
|
|
'NativeModule internal/trace_events',
|
|
]);
|
|
|
|
expected.atRunTime = new Set([
|
|
'NativeModule internal/process/pre_execution',
|
|
]);
|
|
|
|
const { isMainThread } = require('worker_threads');
|
|
// Binaries built without the snapshot (e.g. cross-compiled) and
|
|
// --no-node-snapshot bootstrap the context from scratch; so do workers under
|
|
// --no-worker-snapshot.
|
|
const contextFromSnapshot =
|
|
process.config.variables.node_use_node_snapshot &&
|
|
!process.execArgv.includes('--no-node-snapshot') &&
|
|
(isMainThread || !process.execArgv.includes('--no-worker-snapshot'));
|
|
|
|
if (contextFromSnapshot) {
|
|
[
|
|
'Internal Binding cjs_lexer',
|
|
'NativeModule internal/modules/esm/assert',
|
|
'NativeModule internal/modules/esm/loader',
|
|
'NativeModule internal/modules/esm/load',
|
|
'NativeModule internal/modules/esm/resolve',
|
|
'NativeModule internal/modules/esm/translators',
|
|
'NativeModule internal/modules/esm/module_job',
|
|
'NativeModule internal/modules/esm/module_map',
|
|
'NativeModule url',
|
|
'Internal Binding encoding_binding',
|
|
'NativeModule internal/blob',
|
|
'NativeModule internal/data_url',
|
|
'NativeModule internal/dns/utils',
|
|
'NativeModule internal/encoding',
|
|
'NativeModule internal/encoding/single-byte',
|
|
'NativeModule internal/encoding/util',
|
|
'NativeModule internal/mime',
|
|
'NativeModule internal/modules/typescript',
|
|
'NativeModule internal/net',
|
|
].forEach(expected.beforePreExec.add.bind(expected.beforePreExec));
|
|
} else if (isMainThread) {
|
|
expected.beforePreExec.delete(getFormatNativeModule);
|
|
expected.atRunTime.add(getFormatNativeModule);
|
|
}
|
|
if (!isMainThread) {
|
|
[
|
|
'NativeModule diagnostics_channel',
|
|
'NativeModule internal/abort_controller',
|
|
'NativeModule internal/process/worker_thread_only',
|
|
'NativeModule internal/streams/add-abort-signal',
|
|
'NativeModule internal/streams/compose',
|
|
'NativeModule internal/streams/destroy',
|
|
'NativeModule internal/streams/duplex',
|
|
'NativeModule internal/streams/duplexpair',
|
|
'NativeModule internal/streams/end-of-stream',
|
|
'NativeModule internal/streams/from',
|
|
'NativeModule internal/streams/legacy',
|
|
'NativeModule internal/streams/operators',
|
|
'NativeModule internal/streams/passthrough',
|
|
'NativeModule internal/streams/pipeline',
|
|
'NativeModule internal/streams/readable',
|
|
'NativeModule internal/streams/state',
|
|
'NativeModule internal/streams/transform',
|
|
'NativeModule internal/streams/utils',
|
|
'NativeModule internal/streams/writable',
|
|
'NativeModule internal/worker',
|
|
'NativeModule internal/worker/io',
|
|
'NativeModule internal/worker/messaging',
|
|
'NativeModule stream',
|
|
'NativeModule stream/promises',
|
|
'NativeModule string_decoder',
|
|
'NativeModule worker_threads',
|
|
].forEach(expected.atRunTime.add.bind(expected.atRunTime));
|
|
// For now we'll concatenate the two stages for workers. We prefer
|
|
// atRunTime here because that's what currently happens for these.
|
|
}
|
|
|
|
if (common.isWindows) {
|
|
// On Windows fs needs SideEffectFreeRegExpPrototypeExec which uses vm.
|
|
expected.atRunTime.add('NativeModule vm');
|
|
}
|
|
|
|
if (common.hasIntl) {
|
|
expected.beforePreExec.add('Internal Binding icu');
|
|
}
|
|
|
|
if (process.features.inspector) {
|
|
expected.beforePreExec.add('Internal Binding inspector');
|
|
expected.beforePreExec.add('NativeModule internal/util/inspector');
|
|
expected.beforePreExec.add('NativeModule internal/inspector_async_hook');
|
|
}
|
|
|
|
// This is loaded if the test is run with NODE_V8_COVERAGE.
|
|
if (process.env.NODE_V8_COVERAGE) {
|
|
expected.atRunTime.add('Internal Binding profiler');
|
|
}
|
|
|
|
// Accumulate all the errors and print them at the end instead of throwing
|
|
// immediately which makes it harder to update the test.
|
|
const errorLogs = [];
|
|
function err(message) {
|
|
if (typeof message === 'string') {
|
|
errorLogs.push(message);
|
|
} else {
|
|
// Show the items in individual lines for easier copy-pasting.
|
|
errorLogs.push(inspect(message, { compact: false }));
|
|
}
|
|
}
|
|
|
|
if (isMainThread) {
|
|
const missing = expected.beforePreExec.difference(actual.beforePreExec);
|
|
const extra = actual.beforePreExec.difference(expected.beforePreExec);
|
|
if (missing.size !== 0) {
|
|
err('These builtins are now no longer loaded before pre-execution.');
|
|
err('If this is intentional, remove them from `expected.beforePreExec`.');
|
|
err('\n--- These could be removed from expected.beforePreExec ---');
|
|
err([...missing].sort());
|
|
err('');
|
|
}
|
|
if (extra.size !== 0) {
|
|
err('These builtins are now unexpectedly loaded before pre-execution.');
|
|
err('If this is intentional, add them to `expected.beforePreExec`.');
|
|
err('\n# Note: loading more builtins before pre-execution can lead to ' +
|
|
'startup performance regression or invalid snapshots.');
|
|
err('- Consider lazy loading builtins that are not used universally.');
|
|
err('- Make sure that the builtins do not access environment dependent ' +
|
|
'states e.g. command line arguments or environment variables ' +
|
|
'during loading.');
|
|
err('- When in doubt, ask @nodejs/startup.');
|
|
err('\n--- These could be added to expected.beforePreExec ---');
|
|
err([...extra].sort());
|
|
err('');
|
|
}
|
|
}
|
|
|
|
if (!isMainThread) {
|
|
// For workers, just merge beforePreExec into atRunTime for now.
|
|
// When we start adding modules to the worker snapshot, this branch
|
|
// can be removed and we can just remove the isMainThread
|
|
// conditions.
|
|
expected.beforePreExec.forEach(expected.atRunTime.add.bind(expected.atRunTime));
|
|
actual.beforePreExec.forEach(actual.atRunTime.add.bind(actual.atRunTime));
|
|
}
|
|
|
|
{
|
|
const missing = expected.atRunTime.difference(actual.atRunTime);
|
|
const extra = actual.atRunTime.difference(expected.atRunTime);
|
|
if (missing.size !== 0) {
|
|
err('These builtins are now no longer loaded at run time.');
|
|
err('If this is intentional, remove them from `expected.atRunTime`.');
|
|
err('\n--- These could be removed from expected.atRunTime ---');
|
|
err([...missing].sort());
|
|
err('');
|
|
}
|
|
if (extra.size !== 0) {
|
|
err('These builtins are now unexpectedly loaded at run time.');
|
|
err('If this is intentional, add them to `expected.atRunTime`.');
|
|
err('\n# Note: loading more builtins at run time can lead to ' +
|
|
'startup performance regression.');
|
|
err('- Consider lazy loading builtins that are not used universally.');
|
|
err('\n--- These could be added to expected.atRunTime ---');
|
|
err([...extra].sort());
|
|
err('');
|
|
}
|
|
}
|
|
|
|
assert.strictEqual(errorLogs.length, 0, errorLogs.join('\n'));
|