Skip to content

Commit 43e09af

Browse files
authored
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 28fe494 commit 43e09af

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
@@ -220,7 +220,8 @@ void SetIsolateCreateParamsForNode(Isolate::CreateParams* params) {
220220
const uint64_t total_memory = constrained_memory > 0 ?
221221
std::min(uv_get_total_memory(), constrained_memory) :
222222
uv_get_total_memory();
223-
if (total_memory > 0) {
223+
if (total_memory > 0 &&
224+
params->constraints.max_old_generation_size_in_bytes() == 0) {
224225
// V8 defaults to 700MB or 1.4GB on 32 and 64 bit platforms respectively.
225226
// This default is based on browser use-cases. Tell V8 to configure the
226227
// heap based on the actual physical memory.
@@ -305,17 +306,35 @@ void SetIsolateUpForNode(v8::Isolate* isolate) {
305306
// careful about what we override in the params.
306307
Isolate* NewIsolate(Isolate::CreateParams* params,
307308
uv_loop_t* event_loop,
308-
MultiIsolatePlatform* platform) {
309+
MultiIsolatePlatform* platform,
310+
bool has_snapshot_data) {
309311
Isolate* isolate = Isolate::Allocate();
310312
if (isolate == nullptr) return nullptr;
313+
#ifdef NODE_V8_SHARED_RO_HEAP
314+
{
315+
// In shared-readonly-heap mode, V8 requires all snapshots used for
316+
// creating Isolates to be identical. This isn't really memory-safe
317+
// but also otherwise just doesn't work, and the only real alternative
318+
// is disabling shared-readonly-heap mode altogether.
319+
static Isolate::CreateParams first_params = *params;
320+
params->snapshot_blob = first_params.snapshot_blob;
321+
params->external_references = first_params.external_references;
322+
}
323+
#endif
311324

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

316329
SetIsolateCreateParamsForNode(params);
317330
Isolate::Initialize(isolate, *params);
318-
SetIsolateUpForNode(isolate);
331+
if (!has_snapshot_data) {
332+
// If in deserialize mode, delay until after the deserialization is
333+
// complete.
334+
SetIsolateUpForNode(isolate);
335+
} else {
336+
SetIsolateMiscHandlers(isolate, {});
337+
}
319338

320339
return isolate;
321340
}

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
@@ -148,14 +148,10 @@ class WorkerThreadData {
148148
ArrayBufferAllocator::Create();
149149
Isolate::CreateParams params;
150150
SetIsolateCreateParamsForNode(&params);
151-
params.array_buffer_allocator_shared = allocator;
152-
153-
if (w->snapshot_data() != nullptr) {
154-
SnapshotBuilder::InitializeIsolateParams(w->snapshot_data(), &params);
155-
}
156151
w->UpdateResourceConstraints(&params.constraints);
157-
158-
Isolate* isolate = Isolate::Allocate();
152+
params.array_buffer_allocator_shared = allocator;
153+
Isolate* isolate =
154+
NewIsolate(&params, &loop_, w->platform_, w->snapshot_data());
159155
if (isolate == nullptr) {
160156
// TODO(joyeecheung): maybe this should be kBootstrapFailure instead?
161157
w->Exit(ExitCode::kGenericUserError,
@@ -164,8 +160,6 @@ class WorkerThreadData {
164160
return;
165161
}
166162

167-
w->platform_->RegisterIsolate(isolate, &loop_);
168-
Isolate::Initialize(isolate, params);
169163
SetIsolateUpForNode(isolate);
170164

171165
// 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)