Skip to content

Commit 73d6fbf

Browse files
addaleaxjuanarbol
authored andcommitted
src: fix creating Isolates from addons
daae938 broke addons which create their own `Isolate` instances, because enabling the shared-readonly-heap feature of V8 requires all snapshots used for different `Isolate`s to be identical. Usage of addons that do this has probably decreased quite a bit since Worker threads were introduced in Node.js, but it’s still a valid use case, and in any case the breakage was probably not intentional (although the referenced commit did require test changes because of this issue). This commit addresses this issue partially by caching the V8 snapshot parameters and ignoring ones passed in from users in `NewIsolate()` when this feature is enabled, and makes the `NodeMainInstance` snapshot-based isolate creation also re-use this code. PR-URL: #45885 Reviewed-By: James M Snell <[email protected]>
1 parent 837d481 commit 73d6fbf

9 files changed

+132
-26
lines changed

node.gypi

+3
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,9 @@
9696
'NODE_USE_V8_PLATFORM=0',
9797
],
9898
}],
99+
[ 'v8_enable_shared_ro_heap==1', {
100+
'defines': ['NODE_V8_SHARED_RO_HEAP',],
101+
}],
99102
[ 'node_tag!=""', {
100103
'defines': [ 'NODE_TAG="<(node_tag)"' ],
101104
}],

src/api/environment.cc

+22-3
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,8 @@ void SetIsolateCreateParamsForNode(Isolate::CreateParams* params) {
218218
const uint64_t total_memory = constrained_memory > 0 ?
219219
std::min(uv_get_total_memory(), constrained_memory) :
220220
uv_get_total_memory();
221-
if (total_memory > 0) {
221+
if (total_memory > 0 &&
222+
params->constraints.max_old_generation_size_in_bytes() == 0) {
222223
// V8 defaults to 700MB or 1.4GB on 32 and 64 bit platforms respectively.
223224
// This default is based on browser use-cases. Tell V8 to configure the
224225
// heap based on the actual physical memory.
@@ -303,17 +304,35 @@ void SetIsolateUpForNode(v8::Isolate* isolate) {
303304
// careful about what we override in the params.
304305
Isolate* NewIsolate(Isolate::CreateParams* params,
305306
uv_loop_t* event_loop,
306-
MultiIsolatePlatform* platform) {
307+
MultiIsolatePlatform* platform,
308+
bool has_snapshot_data) {
307309
Isolate* isolate = Isolate::Allocate();
308310
if (isolate == nullptr) return nullptr;
311+
#ifdef NODE_V8_SHARED_RO_HEAP
312+
{
313+
// In shared-readonly-heap mode, V8 requires all snapshots used for
314+
// creating Isolates to be identical. This isn't really memory-safe
315+
// but also otherwise just doesn't work, and the only real alternative
316+
// is disabling shared-readonly-heap mode altogether.
317+
static Isolate::CreateParams first_params = *params;
318+
params->snapshot_blob = first_params.snapshot_blob;
319+
params->external_references = first_params.external_references;
320+
}
321+
#endif
309322

310323
// Register the isolate on the platform before the isolate gets initialized,
311324
// so that the isolate can access the platform during initialization.
312325
platform->RegisterIsolate(isolate, event_loop);
313326

314327
SetIsolateCreateParamsForNode(params);
315328
Isolate::Initialize(isolate, *params);
316-
SetIsolateUpForNode(isolate);
329+
if (!has_snapshot_data) {
330+
// If in deserialize mode, delay until after the deserialization is
331+
// complete.
332+
SetIsolateUpForNode(isolate);
333+
} else {
334+
SetIsolateMiscHandlers(isolate, {});
335+
}
317336

318337
return isolate;
319338
}

src/node_internals.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,8 @@ bool SafeGetenv(const char* key,
303303
void DefineZlibConstants(v8::Local<v8::Object> target);
304304
v8::Isolate* NewIsolate(v8::Isolate::CreateParams* params,
305305
uv_loop_t* event_loop,
306-
MultiIsolatePlatform* platform);
306+
MultiIsolatePlatform* platform,
307+
bool has_snapshot_data = false);
307308
// This overload automatically picks the right 'main_script_id' if no callback
308309
// was provided by the embedder.
309310
v8::MaybeLocal<v8::Value> StartExecution(Environment* env,

src/node_main_instance.cc

+3-13
Original file line numberDiff line numberDiff line change
@@ -76,13 +76,9 @@ NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data,
7676
isolate_params_.get());
7777
}
7878

79-
isolate_ = Isolate::Allocate();
79+
isolate_ = NewIsolate(
80+
isolate_params_.get(), event_loop, platform, snapshot_data != nullptr);
8081
CHECK_NOT_NULL(isolate_);
81-
// Register the isolate on the platform before the isolate gets initialized,
82-
// so that the isolate can access the platform during initialization.
83-
platform->RegisterIsolate(isolate_, event_loop);
84-
SetIsolateCreateParamsForNode(isolate_params_.get());
85-
Isolate::Initialize(isolate_, *isolate_params_);
8682

8783
// If the indexes are not nullptr, we are not deserializing
8884
isolate_data_ = std::make_unique<IsolateData>(
@@ -91,13 +87,7 @@ NodeMainInstance::NodeMainInstance(const SnapshotData* snapshot_data,
9187
platform,
9288
array_buffer_allocator_.get(),
9389
snapshot_data == nullptr ? nullptr : &(snapshot_data->isolate_data_info));
94-
IsolateSettings s;
95-
SetIsolateMiscHandlers(isolate_, s);
96-
if (snapshot_data == nullptr) {
97-
// If in deserialize mode, delay until after the deserialization is
98-
// complete.
99-
SetIsolateErrorHandlers(isolate_, s);
100-
}
90+
10191
isolate_data_->max_young_gen_size =
10292
isolate_params_->constraints.max_young_generation_size_in_bytes();
10393
}

src/node_worker.cc

+3-9
Original file line numberDiff line numberDiff line change
@@ -147,21 +147,15 @@ class WorkerThreadData {
147147
ArrayBufferAllocator::Create();
148148
Isolate::CreateParams params;
149149
SetIsolateCreateParamsForNode(&params);
150-
params.array_buffer_allocator_shared = allocator;
151-
152-
if (w->snapshot_data() != nullptr) {
153-
SnapshotBuilder::InitializeIsolateParams(w->snapshot_data(), &params);
154-
}
155150
w->UpdateResourceConstraints(&params.constraints);
156-
157-
Isolate* isolate = Isolate::Allocate();
151+
params.array_buffer_allocator_shared = allocator;
152+
Isolate* isolate =
153+
NewIsolate(&params, &loop_, w->platform_, w->snapshot_data());
158154
if (isolate == nullptr) {
159155
w->Exit(1, "ERR_WORKER_INIT_FAILED", "Failed to create new Isolate");
160156
return;
161157
}
162158

163-
w->platform_->RegisterIsolate(isolate, &loop_);
164-
Isolate::Initialize(isolate, params);
165159
SetIsolateUpForNode(isolate);
166160

167161
// Be sure it's called before Environment::InitializeDiagnostics()
+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#include <assert.h>
2+
#include <node.h>
3+
4+
using node::Environment;
5+
using node::MultiIsolatePlatform;
6+
using v8::Context;
7+
using v8::FunctionCallbackInfo;
8+
using v8::HandleScope;
9+
using v8::Isolate;
10+
using v8::Local;
11+
using v8::Locker;
12+
using v8::MaybeLocal;
13+
using v8::Object;
14+
using v8::SharedArrayBuffer;
15+
using v8::String;
16+
using v8::Unlocker;
17+
using v8::Value;
18+
19+
void RunInSeparateIsolate(const FunctionCallbackInfo<Value>& args) {
20+
Isolate* parent_isolate = args.GetIsolate();
21+
22+
assert(args[0]->IsString());
23+
String::Utf8Value code(parent_isolate, args[0]);
24+
assert(*code != nullptr);
25+
assert(args[1]->IsSharedArrayBuffer());
26+
auto arg_bs = args[1].As<SharedArrayBuffer>()->GetBackingStore();
27+
28+
Environment* parent_env =
29+
node::GetCurrentEnvironment(parent_isolate->GetCurrentContext());
30+
assert(parent_env != nullptr);
31+
MultiIsolatePlatform* platform = node::GetMultiIsolatePlatform(parent_env);
32+
assert(parent_env != nullptr);
33+
34+
{
35+
Unlocker unlocker(parent_isolate);
36+
37+
std::vector<std::string> errors;
38+
const std::vector<std::string> empty_args;
39+
auto setup =
40+
node::CommonEnvironmentSetup::Create(platform,
41+
&errors,
42+
empty_args,
43+
empty_args,
44+
node::EnvironmentFlags::kNoFlags);
45+
assert(setup);
46+
47+
{
48+
Locker locker(setup->isolate());
49+
Isolate::Scope isolate_scope(setup->isolate());
50+
HandleScope handle_scope(setup->isolate());
51+
Context::Scope context_scope(setup->context());
52+
auto arg = SharedArrayBuffer::New(setup->isolate(), arg_bs);
53+
auto result = setup->context()->Global()->Set(
54+
setup->context(),
55+
v8::String::NewFromUtf8Literal(setup->isolate(), "arg"),
56+
arg);
57+
assert(!result.IsNothing());
58+
59+
MaybeLocal<Value> loadenv_ret =
60+
node::LoadEnvironment(setup->env(), *code);
61+
assert(!loadenv_ret.IsEmpty());
62+
63+
(void)node::SpinEventLoop(setup->env());
64+
65+
node::Stop(setup->env());
66+
}
67+
}
68+
}
69+
70+
void Initialize(Local<Object> exports,
71+
Local<Value> module,
72+
Local<Context> context) {
73+
NODE_SET_METHOD(exports, "runInSeparateIsolate", RunInSeparateIsolate);
74+
}
75+
76+
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
'targets': [
3+
{
4+
'target_name': 'binding',
5+
'sources': [ 'binding.cc' ],
6+
'includes': ['../common.gypi'],
7+
}
8+
]
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Flags: --no-node-snapshot
2+
'use strict';
3+
require('../../common');
4+
5+
// Just re-execute the main test.
6+
require('./test');

test/addons/new-isolate-addon/test.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
const common = require('../../common');
3+
const binding = require(`./build/${common.buildType}/binding`);
4+
const assert = require('assert');
5+
6+
const arg = new SharedArrayBuffer(1);
7+
binding.runInSeparateIsolate('const arr = new Uint8Array(arg); arr[0] = 0x42;', arg);
8+
assert.deepStrictEqual(new Uint8Array(arg), new Uint8Array([0x42]));

0 commit comments

Comments
 (0)