Skip to content

Commit 2d911be

Browse files
committed
bootstrap: make snapshot reproducible
This patch uses the new V8 API to {de}serialize context slots for snapshot in order to make the snapshot reproducible. Also added a test for the reproducibility of snapshots.
1 parent 759ee85 commit 2d911be

File tree

4 files changed

+98
-3
lines changed

4 files changed

+98
-3
lines changed

src/api/environment.cc

+7-1
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,13 @@ Environment* CreateEnvironment(
458458
if (use_snapshot) {
459459
context = Context::FromSnapshot(isolate,
460460
SnapshotData::kNodeMainContextIndex,
461-
{DeserializeNodeInternalFields, env})
461+
v8::DeserializeInternalFieldsCallback(
462+
DeserializeNodeInternalFields, env),
463+
nullptr,
464+
MaybeLocal<Value>(),
465+
nullptr,
466+
v8::DeserializeContextDataCallback(
467+
DeserializeNodeContextData, env))
462468
.ToLocalChecked();
463469

464470
CHECK(!context.IsEmpty());

src/node_snapshotable.cc

+54-2
Original file line numberDiff line numberDiff line change
@@ -1155,8 +1155,11 @@ ExitCode SnapshotBuilder::CreateSnapshot(SnapshotData* out,
11551155
CHECK_EQ(index, SnapshotData::kNodeVMContextIndex);
11561156
index = creator->AddContext(base_context);
11571157
CHECK_EQ(index, SnapshotData::kNodeBaseContextIndex);
1158-
index = creator->AddContext(main_context,
1159-
{SerializeNodeContextInternalFields, env});
1158+
index = creator->AddContext(
1159+
main_context,
1160+
v8::SerializeInternalFieldsCallback(SerializeNodeContextInternalFields,
1161+
env),
1162+
v8::SerializeContextDataCallback(SerializeNodeContextData, env));
11601163
CHECK_EQ(index, SnapshotData::kNodeMainContextIndex);
11611164
}
11621165

@@ -1255,6 +1258,17 @@ std::string SnapshotableObject::GetTypeName() const {
12551258
}
12561259
}
12571260

1261+
void DeserializeNodeContextData(Local<Context> holder,
1262+
int index,
1263+
StartupData payload,
1264+
void* callback_data) {
1265+
DCHECK(index == ContextEmbedderIndex::kEnvironment ||
1266+
index == ContextEmbedderIndex::kRealm ||
1267+
index == ContextEmbedderIndex::kContextTag);
1268+
// This is a no-op for now. We will reset all the pointers in
1269+
// Environment::AssignToContext() via the realm constructor.
1270+
}
1271+
12581272
void DeserializeNodeInternalFields(Local<Object> holder,
12591273
int index,
12601274
StartupData payload,
@@ -1320,6 +1334,44 @@ void DeserializeNodeInternalFields(Local<Object> holder,
13201334
}
13211335
}
13221336

1337+
StartupData SerializeNodeContextData(Local<Context> holder,
1338+
int index,
1339+
void* callback_data) {
1340+
DCHECK(index == ContextEmbedderIndex::kEnvironment ||
1341+
index == ContextEmbedderIndex::kContextifyContext ||
1342+
index == ContextEmbedderIndex::kRealm ||
1343+
index == ContextEmbedderIndex::kContextTag);
1344+
void* data = holder->GetAlignedPointerFromEmbedderData(index);
1345+
per_process::Debug(DebugCategory::MKSNAPSHOT,
1346+
"Serialize context data, index=%d, holder=%p, ptr=%p\n",
1347+
static_cast<int>(index),
1348+
*holder,
1349+
data);
1350+
// Serialization of contextify context is not yet supported.
1351+
if (index == ContextEmbedderIndex::kContextifyContext) {
1352+
DCHECK_NULL(data);
1353+
return {nullptr, 0};
1354+
}
1355+
1356+
// We need to use use new[] because V8 calls delete[] on the returned data.
1357+
int size = sizeof(ContextEmbedderIndex);
1358+
char* result = new char[size];
1359+
ContextEmbedderIndex* index_data =
1360+
reinterpret_cast<ContextEmbedderIndex*>(result);
1361+
*index_data = static_cast<ContextEmbedderIndex>(index);
1362+
1363+
// For now we just reset all of them in Environment::AssignToContext()
1364+
switch (index) {
1365+
case ContextEmbedderIndex::kEnvironment:
1366+
case ContextEmbedderIndex::kContextifyContext:
1367+
case ContextEmbedderIndex::kRealm:
1368+
case ContextEmbedderIndex::kContextTag:
1369+
return StartupData{result, size};
1370+
default:
1371+
UNREACHABLE();
1372+
}
1373+
}
1374+
13231375
StartupData SerializeNodeContextInternalFields(Local<Object> holder,
13241376
int index,
13251377
void* callback_data) {

src/node_snapshotable.h

+7
Original file line numberDiff line numberDiff line change
@@ -126,10 +126,17 @@ class SnapshotableObject : public BaseObject {
126126
v8::StartupData SerializeNodeContextInternalFields(v8::Local<v8::Object> holder,
127127
int index,
128128
void* env);
129+
v8::StartupData SerializeNodeContextData(v8::Local<v8::Context> holder,
130+
int index,
131+
void* env);
129132
void DeserializeNodeInternalFields(v8::Local<v8::Object> holder,
130133
int index,
131134
v8::StartupData payload,
132135
void* env);
136+
void DeserializeNodeContextData(v8::Local<v8::Context> holder,
137+
int index,
138+
v8::StartupData payload,
139+
void* env);
133140
void SerializeSnapshotableObjects(Realm* realm,
134141
v8::SnapshotCreator* creator,
135142
RealmSerializeInfo* info);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
'use strict';
2+
3+
require('../common');
4+
const { spawnSyncAndExitWithoutError } = require('../common/child_process');
5+
const tmpdir = require('../common/tmpdir');
6+
const fs = require('fs');
7+
const assert = require('assert');
8+
9+
function generateSnapshot() {
10+
tmpdir.refresh();
11+
12+
spawnSyncAndExitWithoutError(
13+
process.execPath,
14+
[
15+
'--random_seed=42',
16+
'--predictable',
17+
'--build-snapshot',
18+
'node:generate_default_snapshot',
19+
],
20+
{
21+
cwd: tmpdir.path
22+
}
23+
);
24+
const blobPath = tmpdir.resolve('snapshot.blob');
25+
return fs.readFileSync(blobPath);
26+
}
27+
28+
const buf1 = generateSnapshot();
29+
const buf2 = generateSnapshot();
30+
assert.deepStrictEqual(buf1, buf2);

0 commit comments

Comments
 (0)