mirror of
https://github.com/electron/electron.git
synced 2026-10-28 19:54:46 +08:00
Since #51703 the browser process boots from the embedded Node.js startup snapshot on native builds, but every other process that hosts Node.js -- renderers with Node integration or an unsandboxed preload, Node-enabled web workers, the utility-process node service, and ELECTRON_RUN_AS_NODE / child_process.fork() children -- still ran the full bootstrap, compiling the ~120-140 builtins it loads from source on every start (and then serializing a fresh code cache for each of them that nothing consumed). Three pieces: 1. Build-time code cache for Node's own builtins in every process flavor. The electron_natives_codecache tool now also emits a V8 code cache for Node's lib/ builtins (all but internal/deps/*) for the flavors whose processes bootstrap Node from scratch, compiled against that flavor's isolate and flag set like the electron/js2c/* bundles already were. The cache has to be in place before node::NewContext and node::CreateEnvironment run the bootstrap, i.e. before Electron can reach env->builtin_loader(), so it is installed as a process-wide default every BuiltinLoader is seeded with (new BuiltinLoader::SetProcessDefaultCodeCache() hook in the js2c code-cache node patch), from JavascriptEnvironment / NodeBindings::Initialize / the web-worker path. GN decides per flavor whether Node's builtins are embedded (always for the renderer flavor; utility/browser only on builds without a Node snapshot), so native builds pay for one copy. 2. ELECTRON_RUN_AS_NODE children and the utility-process node service create their isolate from the embedded Node snapshot too. Each has exactly one JavascriptEnvironment isolate (worker threads inherit the snapshot through IsolateData), so JavascriptEnvironment hands them the snapshot like the browser process, CreateIsolateData gets its EmbedderSnapshotData, CreateEnvironment an empty context, the deserialized context is entered, and the duplicate per-isolate message listener the snapshot path already installed is skipped. Cross builds and custom-V8-snapshot setups keep the from-scratch path. The js2c cache flavor now follows what the isolate was actually created from (ELECTRON_RUN_AS_NODE children were previously fed the browser flavor keyed to the Node-snapshot isolate, so even their node_init cache was silently rejected). 3. A node_platform fix this exposed: WorkerThreadsTaskRunner's DelayedTaskScheduler drains its queue through a TaskQueue, which is a std::priority_queue treating plain v8::Tasks as equal, so a StopTask can run before an earlier-queued ScheduleTask; that then arms a timer on the stopped loop and NodePlatform::Shutdown() waits it out -- observed as ~10 % of forked children stalling exactly 8 s (V8's memory-reducer delay) at exit in release builds. New patch drops ScheduleTasks that arrive after StopTask; upstreamable. The DCHECK-only cache-status record now covers all builtins, and the js2c code-cache spec asserts that browser / renderer / utility / run-as-node compile no Node builtin without consuming a cache entry, once as built and once from a copy of the app with the LoadBrowserProcessSpecificV8Snapshot fuse enabled. Whether a process may use the Node snapshot is decided by comparing the blob it loaded with the built-in one alone, so the fuse only affects the process whose blob it actually swaps. spec/node-spec.ts, utility-process, net, modules, asar, esm, context-bridge, ipc-renderer specs pass locally. Linux x64 release build (official, ThinLTO, PGO), interleaved A/B vs main, n=40 fresh processes per cell: ELECTRON_RUN_AS_NODE=1 electron -e 0 82.5 -> 37.0 ms child_process.fork(noop) -> 'exit' 89.4 -> 41.1 ms utilityProcess.fork -> first message 86.1 -> 39.7 ms nodeIntegration renderer, timeOrigin -> first preload statement 90.9 -> 64.7 ms stripped binary +2.0 MB
59 lines
2.4 KiB
C++
59 lines
2.4 KiB
C++
// Copyright (c) 2026 Anthropic, PBC.
|
|
// Use of this source code is governed by the MIT license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "shell/common/node_natives_code_cache.h"
|
|
|
|
#include "shell/browser/javascript_environment.h"
|
|
#include "shell/common/node_includes.h"
|
|
#include "shell/common/node_natives_code_cache_internal.h"
|
|
#include "shell/common/process_util.h"
|
|
#include "third_party/electron_node/src/node_snapshot_builder.h"
|
|
|
|
namespace electron {
|
|
|
|
Js2cCacheFlavor CurrentProcessJs2cCacheFlavor(bool has_node_env) {
|
|
if (IsBrowserProcess() || IsUtilityProcess()) {
|
|
// Processes that host Node through a JavascriptEnvironment: the browser
|
|
// process, ELECTRON_RUN_AS_NODE children (no --type either) and the node
|
|
// service's utility process. On builds that embed a Node startup snapshot
|
|
// their isolate normally comes from that snapshot, which is what the
|
|
// browser flavor is keyed to there. When such a process nevertheless boots
|
|
// from the v8 context snapshot (a custom V8 snapshot is loaded), its
|
|
// isolate and flag set match the utility flavor instead (which then lacks
|
|
// browser_init; it compiles from source). Without an embedded snapshot both
|
|
// flavors are keyed to the v8 context snapshot and split by init bundle.
|
|
if (JavascriptEnvironment::NodeSnapshot())
|
|
return Js2cCacheFlavor::kBrowser;
|
|
const bool build_embeds_node_snapshot =
|
|
node::SnapshotBuilder::GetEmbeddedSnapshotData() != nullptr;
|
|
return IsUtilityProcess() || build_embeds_node_snapshot
|
|
? Js2cCacheFlavor::kUtility
|
|
: Js2cCacheFlavor::kBrowser;
|
|
}
|
|
if (IsRendererProcess()) {
|
|
// No Node env means the V8 flag set stays frozen -- a distinct flavor.
|
|
return has_node_env ? Js2cCacheFlavor::kRenderer
|
|
: Js2cCacheFlavor::kSandbox;
|
|
}
|
|
return Js2cCacheFlavor::kWorker;
|
|
}
|
|
|
|
const std::vector<node::builtins::CodeCacheInfo>& GetNativesCodeCache(
|
|
Js2cCacheFlavor flavor) {
|
|
switch (flavor) {
|
|
case Js2cCacheFlavor::kSandbox:
|
|
return internal::Js2cCacheSandbox();
|
|
case Js2cCacheFlavor::kRenderer:
|
|
return internal::Js2cCacheRenderer();
|
|
case Js2cCacheFlavor::kBrowser:
|
|
return internal::Js2cCacheBrowser();
|
|
case Js2cCacheFlavor::kUtility:
|
|
return internal::Js2cCacheUtility();
|
|
case Js2cCacheFlavor::kWorker:
|
|
return internal::Js2cCacheWorker();
|
|
}
|
|
}
|
|
|
|
} // namespace electron
|