Browse Source

fix: support for v8.setHeapSnapshotNearHeapLimit api (#45606)

* fix: support for v8.setHeapSnapshotNearHeapLimit api

* docs: add  support
Robo 2 months ago
parent
commit
137a552641

+ 6 - 0
docs/api/command-line-switches.md

@@ -313,6 +313,12 @@ Set the default value of the `verbatim` parameter in the Node.js [`dns.lookup()`
 
 The default is `verbatim` and `dns.setDefaultResultOrder()` have higher priority than `--dns-result-order`.
 
+### `--diagnostic-dir=directory`
+
+Set the directory to which all Node.js diagnostic output files are written. Defaults to current working directory.
+
+Affects the default output directory of [v8.setHeapSnapshotNearHeapLimit](https://nodejs.org/docs/latest/api/v8.html#v8setheapsnapshotnearheaplimitlimit).
+
 [app]: app.md
 [append-switch]: command-line.md#commandlineappendswitchswitch-value
 [debugging-main-process]: ../tutorial/debugging-main-process.md

+ 2 - 1
shell/browser/electron_browser_main_parts.cc

@@ -237,7 +237,8 @@ void ElectronBrowserMainParts::PostEarlyInitialization() {
   node_bindings_->Initialize(js_env_->isolate()->GetCurrentContext());
   // Create the global environment.
   node_env_ = node_bindings_->CreateEnvironment(
-      js_env_->isolate()->GetCurrentContext(), js_env_->platform());
+      js_env_->isolate()->GetCurrentContext(), js_env_->platform(),
+      js_env_->max_young_generation_size_in_bytes());
 
   node_env_->set_trace_sync_io(node_env_->options()->trace_sync_io);
 

+ 9 - 2
shell/browser/javascript_environment.cc

@@ -33,9 +33,15 @@ namespace electron {
 
 namespace {
 
-gin::IsolateHolder CreateIsolateHolder(v8::Isolate* isolate) {
+gin::IsolateHolder CreateIsolateHolder(v8::Isolate* isolate,
+                                       size_t* max_young_generation_size) {
   std::unique_ptr<v8::Isolate::CreateParams> create_params =
       gin::IsolateHolder::getDefaultIsolateParams();
+  // The value is needed to adjust heap limit when capturing
+  // snapshot via v8.setHeapSnapshotNearHeapLimit(limit) or
+  // --heapsnapshot-near-heap-limit=max_count.
+  *max_young_generation_size =
+      create_params->constraints.max_young_generation_size_in_bytes();
   // Align behavior with V8 Isolate default for Node.js.
   // This is necessary for important aspects of Node.js
   // including heap and cpu profilers to function properly.
@@ -55,7 +61,8 @@ gin::IsolateHolder CreateIsolateHolder(v8::Isolate* isolate) {
 JavascriptEnvironment::JavascriptEnvironment(uv_loop_t* event_loop,
                                              bool setup_wasm_streaming)
     : isolate_holder_{CreateIsolateHolder(
-          Initialize(event_loop, setup_wasm_streaming))},
+          Initialize(event_loop, setup_wasm_streaming),
+          &max_young_generation_size_)},
       isolate_{isolate_holder_.isolate()},
       locker_{isolate_} {
   isolate_->Enter();

+ 4 - 0
shell/browser/javascript_environment.h

@@ -36,6 +36,9 @@ class JavascriptEnvironment {
 
   node::MultiIsolatePlatform* platform() const { return platform_.get(); }
   v8::Isolate* isolate() const { return isolate_; }
+  size_t max_young_generation_size_in_bytes() const {
+    return max_young_generation_size_;
+  }
 
   static v8::Isolate* GetIsolate();
 
@@ -43,6 +46,7 @@ class JavascriptEnvironment {
   v8::Isolate* Initialize(uv_loop_t* event_loop, bool setup_wasm_streaming);
   std::unique_ptr<node::MultiIsolatePlatform> platform_;
 
+  size_t max_young_generation_size_ = 0;
   gin::IsolateHolder isolate_holder_;
 
   // owned-by: isolate_holder_

+ 6 - 1
shell/common/node_bindings.cc

@@ -324,6 +324,7 @@ bool IsAllowedOption(const std::string_view option) {
 
   // This should be aligned with what's possible to set via the process object.
   static constexpr auto options = base::MakeFixedFlatSet<std::string_view>({
+      "--diagnostic-dir",
       "--dns-result-order",
       "--no-deprecation",
       "--throw-deprecation",
@@ -596,6 +597,7 @@ void NodeBindings::Initialize(v8::Local<v8::Context> context) {
 std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
     v8::Local<v8::Context> context,
     node::MultiIsolatePlatform* platform,
+    size_t max_young_generation_size,
     std::vector<std::string> args,
     std::vector<std::string> exec_args,
     std::optional<base::RepeatingCallback<void()>> on_app_code_ready) {
@@ -646,6 +648,7 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
   args.insert(args.begin() + 1, init_script);
 
   auto* isolate_data = node::CreateIsolateData(isolate, uv_loop_, platform);
+  isolate_data->max_young_gen_size = max_young_generation_size;
   context->SetAlignedPointerInEmbedderData(kElectronContextEmbedderDataIndex,
                                            static_cast<void*>(isolate_data));
 
@@ -783,8 +786,10 @@ std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
 std::shared_ptr<node::Environment> NodeBindings::CreateEnvironment(
     v8::Local<v8::Context> context,
     node::MultiIsolatePlatform* platform,
+    size_t max_young_generation_size,
     std::optional<base::RepeatingCallback<void()>> on_app_code_ready) {
-  return CreateEnvironment(context, platform, ElectronCommandLine::AsUtf8(), {},
+  return CreateEnvironment(context, platform, max_young_generation_size,
+                           ElectronCommandLine::AsUtf8(), {},
                            on_app_code_ready);
 }
 

+ 2 - 0
shell/common/node_bindings.h

@@ -133,6 +133,7 @@ class NodeBindings {
   std::shared_ptr<node::Environment> CreateEnvironment(
       v8::Local<v8::Context> context,
       node::MultiIsolatePlatform* platform,
+      size_t max_young_generation_size,
       std::vector<std::string> args,
       std::vector<std::string> exec_args,
       std::optional<base::RepeatingCallback<void()>> on_app_code_ready =
@@ -141,6 +142,7 @@ class NodeBindings {
   std::shared_ptr<node::Environment> CreateEnvironment(
       v8::Local<v8::Context> context,
       node::MultiIsolatePlatform* platform,
+      size_t max_young_generation_size = 0,
       std::optional<base::RepeatingCallback<void()>> on_app_code_ready =
           std::nullopt);
 

+ 1 - 1
shell/renderer/electron_renderer_client.cc

@@ -109,7 +109,7 @@ void ElectronRendererClient::DidCreateScriptContext(
       blink::LoaderFreezeMode::kStrict);
 
   std::shared_ptr<node::Environment> env = node_bindings_->CreateEnvironment(
-      renderer_context, nullptr,
+      renderer_context, nullptr, 0,
       base::BindRepeating(&ElectronRendererClient::UndeferLoad,
                           base::Unretained(this), render_frame));
 

+ 2 - 1
shell/services/node/node_service.cc

@@ -137,7 +137,8 @@ void NodeService::Initialize(
   // Create the global environment.
   node_env_ = node_bindings_->CreateEnvironment(
       js_env_->isolate()->GetCurrentContext(), js_env_->platform(),
-      params->args, params->exec_args);
+      js_env_->max_young_generation_size_in_bytes(), params->args,
+      params->exec_args);
 
   // Override the default handler set by NodeBindings.
   node_env_->isolate()->SetFatalErrorHandler(V8FatalErrorCallback);

+ 23 - 0
spec/api-utility-process-spec.ts

@@ -5,6 +5,8 @@ import { expect } from 'chai';
 
 import * as childProcess from 'node:child_process';
 import { once } from 'node:events';
+import * as fs from 'node:fs/promises';
+import * as os from 'node:os';
 import * as path from 'node:path';
 import { setImmediate } from 'node:timers/promises';
 import { pathToFileURL } from 'node:url';
@@ -760,5 +762,26 @@ describe('utilityProcess module', () => {
       expect(loginAuthInfo!.realm).to.equal('Foo');
       expect(loginAuthInfo!.scheme).to.equal('basic');
     });
+
+    it('supports generating snapshots via v8.setHeapSnapshotNearHeapLimit', async () => {
+      const tmpDir = await fs.mkdtemp(path.resolve(os.tmpdir(), 'electron-spec-utility-oom-'));
+      const child = utilityProcess.fork(path.join(fixturesPath, 'oom-grow.js'), [], {
+        stdio: 'ignore',
+        execArgv: [
+          `--diagnostic-dir=${tmpDir}`,
+          '--js-flags=--max-old-space-size=50'
+        ],
+        env: {
+          NODE_DEBUG_NATIVE: 'diagnostic'
+        }
+      });
+      await once(child, 'spawn');
+      await once(child, 'exit');
+      const files = (await fs.readdir(tmpDir)).filter((file) => file.endsWith('.heapsnapshot'));
+      expect(files.length).to.be.equal(1);
+      const stat = await fs.stat(path.join(tmpDir, files[0]));
+      expect(stat.size).to.be.greaterThan(0);
+      await fs.rm(tmpDir, { recursive: true });
+    });
   });
 });

+ 11 - 0
spec/fixtures/api/utility-process/oom-grow.js

@@ -0,0 +1,11 @@
+const v8 = require('node:v8');
+
+v8.setHeapSnapshotNearHeapLimit(1);
+
+const arr = [];
+function runAllocation () {
+  const str = JSON.stringify(process.config).slice(0, 1000);
+  arr.push(str);
+  setImmediate(runAllocation);
+}
+setImmediate(runAllocation);