Skip to content

Commit 34e8331

Browse files
joyeecheungtargos
authored andcommittedJul 12, 2022
v8: add v8.startupSnapshot utils
This adds several APIs under the `v8.startupSnapshot` namespace for specifying hooks into the startup snapshot serialization and deserialization. - isBuildingSnapshot() - addSerializeCallback() - addDeserializeCallback() - setDeserializeMainFunction() PR-URL: #43329 Fixes: #42617 Refs: #35711 Reviewed-By: Chengzhong Wu <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent a1f581a commit 34e8331

15 files changed

+413
-29
lines changed
 

‎doc/api/errors.md

+15
Original file line numberDiff line numberDiff line change
@@ -1179,6 +1179,13 @@ because the `node:domain` module has been loaded at an earlier point in time.
11791179
The stack trace is extended to include the point in time at which the
11801180
`node:domain` module had been loaded.
11811181

1182+
<a id="ERR_DUPLICATE_STARTUP_SNAPSHOT_MAIN_FUNCTION"></a>
1183+
1184+
### `ERR_DUPLICATE_STARTUP_SNAPSHOT_MAIN_FUNCTION`
1185+
1186+
[`v8.startupSnapshot.setDeserializeMainFunction()`][] could not be called
1187+
because it had already been called before.
1188+
11821189
<a id="ERR_ENCODING_INVALID_ENCODED_DATA"></a>
11831190

11841191
### `ERR_ENCODING_INVALID_ENCODED_DATA`
@@ -2314,6 +2321,13 @@ has occurred when attempting to start the loop.
23142321
Once no more items are left in the queue, the idle loop must be suspended. This
23152322
error indicates that the idle loop has failed to stop.
23162323

2324+
<a id="ERR_NOT_BUILDING_SNAPSHOT"></a>
2325+
2326+
### `ERR_NOT_BUILDING_SNAPSHOT`
2327+
2328+
An attempt was made to use operations that can only be used when building
2329+
V8 startup snapshot even though Node.js isn't building one.
2330+
23172331
<a id="ERR_NO_CRYPTO"></a>
23182332

23192333
### `ERR_NO_CRYPTO`
@@ -3501,6 +3515,7 @@ The native call from `process.cpuUsage` could not be processed.
35013515
[`url.parse()`]: url.md#urlparseurlstring-parsequerystring-slashesdenotehost
35023516
[`util.getSystemErrorName(error.errno)`]: util.md#utilgetsystemerrornameerr
35033517
[`util.parseArgs()`]: util.md#utilparseargsconfig
3518+
[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data
35043519
[`zlib`]: zlib.md
35053520
[crypto digest algorithm]: crypto.md#cryptogethashes
35063521
[debugger]: debugger.md

‎doc/api/v8.md

+131
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,137 @@ Called immediately after a promise continuation executes. This may be after a
876876
Called when the promise receives a resolution or rejection value. This may
877877
occur synchronously in the case of `Promise.resolve()` or `Promise.reject()`.
878878

879+
## Startup Snapshot API
880+
881+
<!-- YAML
882+
added: REPLACEME
883+
-->
884+
885+
> Stability: 1 - Experimental
886+
887+
The `v8.startupSnapshot` interface can be used to add serialization and
888+
deserialization hooks for custom startup snapshots. Currently the startup
889+
snapshots can only be built into the Node.js binary from source.
890+
891+
```console
892+
$ cd /path/to/node
893+
$ ./configure --node-snapshot-main=entry.js
894+
$ make node
895+
# This binary contains the result of the execution of entry.js
896+
$ out/Release/node
897+
```
898+
899+
In the example above, `entry.js` can use methods from the `v8.startupSnapshot`
900+
interface to specify how to save information for custom objects in the snapshot
901+
during serialization and how the information can be used to synchronize these
902+
objects during deserialization of the snapshot. For example, if the `entry.js`
903+
contains the following script:
904+
905+
```cjs
906+
'use strict';
907+
908+
const fs = require('fs');
909+
const zlib = require('zlib');
910+
const path = require('path');
911+
const assert = require('assert');
912+
913+
const {
914+
isBuildingSnapshot,
915+
addSerializeCallback,
916+
addDeserializeCallback,
917+
setDeserializeMainFunction
918+
} = require('v8').startupSnapshot;
919+
920+
const filePath = path.resolve(__dirname, '../x1024.txt');
921+
const storage = {};
922+
923+
assert(isBuildingSnapshot());
924+
925+
addSerializeCallback(({ filePath }) => {
926+
storage[filePath] = zlib.gzipSync(fs.readFileSync(filePath));
927+
}, { filePath });
928+
929+
addDeserializeCallback(({ filePath }) => {
930+
storage[filePath] = zlib.gunzipSync(storage[filePath]);
931+
}, { filePath });
932+
933+
setDeserializeMainFunction(({ filePath }) => {
934+
console.log(storage[filePath].toString());
935+
}, { filePath });
936+
```
937+
938+
The resulted binary will simply print the data deserialized from the snapshot
939+
during start up:
940+
941+
```console
942+
$ out/Release/node
943+
# Prints content of ./test/fixtures/x1024.txt
944+
```
945+
946+
Currently the API is only available to a Node.js instance launched from the
947+
default snapshot, that is, the application deserialized from a user-land
948+
snapshot cannot use these APIs again.
949+
950+
### `v8.startupSnapshot.addSerializeCallback(callback[, data])`
951+
952+
<!-- YAML
953+
added: REPLACEME
954+
-->
955+
956+
* `callback` {Function} Callback to be invoked before serialization.
957+
* `data` {any} Optional data that will be passed to the `callback` when it
958+
gets called.
959+
960+
Add a callback that will be called when the Node.js instance is about to
961+
get serialized into a snapshot and exit. This can be used to release
962+
resources that should not or cannot be serialized or to convert user data
963+
into a form more suitable for serialization.
964+
965+
### `v8.startupSnapshot.addDeserializeCallback(callback[, data])`
966+
967+
<!-- YAML
968+
added: REPLACEME
969+
-->
970+
971+
* `callback` {Function} Callback to be invoked after the snapshot is
972+
deserialized.
973+
* `data` {any} Optional data that will be passed to the `callback` when it
974+
gets called.
975+
976+
Add a callback that will be called when the Node.js instance is deserialized
977+
from a snapshot. The `callback` and the `data` (if provided) will be
978+
serialized into the snapshot, they can be used to re-initialize the state
979+
of the application or to re-acquire resources that the application needs
980+
when the application is restarted from the snapshot.
981+
982+
### `v8.startupSnapshot.setDeserializeMainFunction(callback[, data])`
983+
984+
<!-- YAML
985+
added: REPLACEME
986+
-->
987+
988+
* `callback` {Function} Callback to be invoked as the entry point after the
989+
snapshot is deserialized.
990+
* `data` {any} Optional data that will be passed to the `callback` when it
991+
gets called.
992+
993+
This sets the entry point of the Node.js application when it is deserialized
994+
from a snapshot. This can be called only once in the snapshot building
995+
script. If called, the deserialized application no longer needs an additional
996+
entry point script to start up and will simply invoke the callback along with
997+
the deserialized data (if provided), otherwise an entry point script still
998+
needs to be provided to the deserialized application.
999+
1000+
### `v8.startupSnapshot.isBuildingSnapshot()`
1001+
1002+
<!-- YAML
1003+
added: REPLACEME
1004+
-->
1005+
1006+
* Returns: {boolean}
1007+
1008+
Returns true if the Node.js instance is run to build a snapshot.
1009+
8791010
[HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
8801011
[Hook Callbacks]: #hook-callbacks
8811012
[V8]: https://developers.google.com/v8/

‎lib/internal/bootstrap/pre_execution.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ function prepareMainThreadExecution(expandArgv1 = false,
5353
setupCoverageHooks(process.env.NODE_V8_COVERAGE);
5454
}
5555

56-
5756
setupDebugEnv();
5857

5958
// Print stack trace on `SIGINT` if option `--trace-sigint` presents.
@@ -84,6 +83,8 @@ function prepareMainThreadExecution(expandArgv1 = false,
8483
initializeDeprecations();
8584
initializeWASI();
8685

86+
require('internal/v8/startup_snapshot').runDeserializeCallbacks();
87+
8788
if (!initialzeModules) {
8889
return;
8990
}

‎lib/internal/bootstrap/switches/is_main_thread.js

+19-4
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22

33
const { ObjectDefineProperty } = primordials;
44
const rawMethods = internalBinding('process_methods');
5-
5+
const {
6+
addSerializeCallback,
7+
isBuildingSnapshot
8+
} = require('v8').startupSnapshot;
69
// TODO(joyeecheung): deprecate and remove these underscore methods
710
process._debugProcess = rawMethods._debugProcess;
811
process._debugEnd = rawMethods._debugEnd;
@@ -134,6 +137,12 @@ function refreshStderrOnSigWinch() {
134137
stderr._refreshSize();
135138
}
136139

140+
function addCleanup(fn) {
141+
if (isBuildingSnapshot()) {
142+
addSerializeCallback(fn);
143+
}
144+
}
145+
137146
function getStdout() {
138147
if (stdout) return stdout;
139148
stdout = createWritableStdioStream(1);
@@ -145,12 +154,14 @@ function getStdout() {
145154
process.on('SIGWINCH', refreshStdoutOnSigWinch);
146155
}
147156

148-
internalBinding('mksnapshot').cleanups.push(function cleanupStdout() {
157+
addCleanup(function cleanupStdout() {
149158
stdout._destroy = stdoutDestroy;
150159
stdout.destroy();
151160
process.removeListener('SIGWINCH', refreshStdoutOnSigWinch);
152161
stdout = undefined;
153162
});
163+
// No need to add deserialize callback because stdout = undefined above
164+
// causes the stream to be lazily initialized again later.
154165
return stdout;
155166
}
156167

@@ -164,12 +175,14 @@ function getStderr() {
164175
if (stderr.isTTY) {
165176
process.on('SIGWINCH', refreshStderrOnSigWinch);
166177
}
167-
internalBinding('mksnapshot').cleanups.push(function cleanupStderr() {
178+
addCleanup(function cleanupStderr() {
168179
stderr._destroy = stderrDestroy;
169180
stderr.destroy();
170181
process.removeListener('SIGWINCH', refreshStderrOnSigWinch);
171182
stderr = undefined;
172183
});
184+
// No need to add deserialize callback because stderr = undefined above
185+
// causes the stream to be lazily initialized again later.
173186
return stderr;
174187
}
175188

@@ -256,10 +269,12 @@ function getStdin() {
256269
}
257270
}
258271

259-
internalBinding('mksnapshot').cleanups.push(function cleanupStdin() {
272+
addCleanup(function cleanupStdin() {
260273
stdin.destroy();
261274
stdin = undefined;
262275
});
276+
// No need to add deserialize callback because stdin = undefined above
277+
// causes the stream to be lazily initialized again later.
263278
return stdin;
264279
}
265280

‎lib/internal/errors.js

+4
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,8 @@ E('ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE',
978978
'The `domain` module is in use, which is mutually exclusive with calling ' +
979979
'process.setUncaughtExceptionCaptureCallback()',
980980
Error);
981+
E('ERR_DUPLICATE_STARTUP_SNAPSHOT_MAIN_FUNCTION',
982+
'Deserialize main function is already configured.', Error);
981983
E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) {
982984
this.errno = ret;
983985
return `The encoded data was not valid for encoding ${encoding}`;
@@ -1456,6 +1458,8 @@ E('ERR_NETWORK_IMPORT_BAD_RESPONSE',
14561458
"import '%s' received a bad response: %s", Error);
14571459
E('ERR_NETWORK_IMPORT_DISALLOWED',
14581460
"import of '%s' by %s is not supported: %s", Error);
1461+
E('ERR_NOT_BUILDING_SNAPSHOT',
1462+
'Operation cannot be invoked when not building startup snapshot', Error);
14591463
E('ERR_NO_CRYPTO',
14601464
'Node.js is not compiled with OpenSSL crypto support', Error);
14611465
E('ERR_NO_ICU',

‎lib/internal/main/mksnapshot.js

+7-10
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const {
99
const binding = internalBinding('mksnapshot');
1010
const { NativeModule } = require('internal/bootstrap/loaders');
1111
const {
12-
compileSnapshotMain,
12+
compileSerializeMain,
1313
} = binding;
1414

1515
const {
@@ -83,7 +83,7 @@ const supportedModules = new SafeSet(new SafeArrayIterator([
8383
'v8',
8484
// 'vm',
8585
// 'worker_threads',
86-
// 'zlib',
86+
'zlib',
8787
]));
8888

8989
const warnedModules = new SafeSet();
@@ -117,25 +117,22 @@ function main() {
117117
} = require('internal/bootstrap/pre_execution');
118118

119119
prepareMainThreadExecution(true, false);
120-
process.once('beforeExit', function runCleanups() {
121-
for (const cleanup of binding.cleanups) {
122-
cleanup();
123-
}
124-
});
125120

126121
const file = process.argv[1];
127122
const path = require('path');
128123
const filename = path.resolve(file);
129124
const dirname = path.dirname(filename);
130125
const source = readFileSync(file, 'utf-8');
131-
const snapshotMainFunction = compileSnapshotMain(filename, source);
126+
const serializeMainFunction = compileSerializeMain(filename, source);
127+
128+
require('internal/v8/startup_snapshot').initializeCallbacks();
132129

133130
if (getOptionValue('--inspect-brk')) {
134131
internalBinding('inspector').callAndPauseOnStart(
135-
snapshotMainFunction, undefined,
132+
serializeMainFunction, undefined,
136133
requireForUserSnapshot, filename, dirname);
137134
} else {
138-
snapshotMainFunction(requireForUserSnapshot, filename, dirname);
135+
serializeMainFunction(requireForUserSnapshot, filename, dirname);
139136
}
140137
}
141138

‎lib/internal/v8/startup_snapshot.js

+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
'use strict';
2+
3+
const {
4+
validateFunction,
5+
} = require('internal/validators');
6+
const {
7+
ERR_NOT_BUILDING_SNAPSHOT,
8+
ERR_DUPLICATE_STARTUP_SNAPSHOT_MAIN_FUNCTION
9+
} = require('internal/errors');
10+
11+
const {
12+
setSerializeCallback,
13+
setDeserializeCallback,
14+
setDeserializeMainFunction: _setDeserializeMainFunction,
15+
markBootstrapComplete
16+
} = internalBinding('mksnapshot');
17+
18+
function isBuildingSnapshot() {
19+
// For now this is the only way to build a snapshot.
20+
return require('internal/options').getOptionValue('--build-snapshot');
21+
}
22+
23+
function throwIfNotBuildingSnapshot() {
24+
if (!isBuildingSnapshot()) {
25+
throw new ERR_NOT_BUILDING_SNAPSHOT();
26+
}
27+
}
28+
29+
const deserializeCallbacks = [];
30+
let deserializeCallbackIsSet = false;
31+
function runDeserializeCallbacks() {
32+
while (deserializeCallbacks.length > 0) {
33+
const { 0: callback, 1: data } = deserializeCallbacks.shift();
34+
callback(data);
35+
}
36+
}
37+
38+
function addDeserializeCallback(callback, data) {
39+
throwIfNotBuildingSnapshot();
40+
validateFunction(callback, 'callback');
41+
if (!deserializeCallbackIsSet) {
42+
// TODO(joyeecheung): when the main function handling is done in JS,
43+
// the deserialize callbacks can always be invoked. For now only
44+
// store it in C++ when it's actually used to avoid unnecessary
45+
// C++ -> JS costs.
46+
setDeserializeCallback(runDeserializeCallbacks);
47+
deserializeCallbackIsSet = true;
48+
}
49+
deserializeCallbacks.push([callback, data]);
50+
}
51+
52+
const serializeCallbacks = [];
53+
function runSerializeCallbacks() {
54+
while (serializeCallbacks.length > 0) {
55+
const { 0: callback, 1: data } = serializeCallbacks.shift();
56+
callback(data);
57+
}
58+
// Remove the hooks from the snapshot.
59+
require('v8').startupSnapshot = undefined;
60+
}
61+
62+
function addSerializeCallback(callback, data) {
63+
throwIfNotBuildingSnapshot();
64+
validateFunction(callback, 'callback');
65+
serializeCallbacks.push([callback, data]);
66+
}
67+
68+
function initializeCallbacks() {
69+
// Only run the serialize callbacks in snapshot building mode, otherwise
70+
// they throw.
71+
if (isBuildingSnapshot()) {
72+
setSerializeCallback(runSerializeCallbacks);
73+
}
74+
}
75+
76+
let deserializeMainIsSet = false;
77+
function setDeserializeMainFunction(callback, data) {
78+
throwIfNotBuildingSnapshot();
79+
// TODO(joyeecheung): In lib/internal/bootstrap/node.js, create a default
80+
// main function to run the lib/internal/main scripts and make sure that
81+
// the main function set in the snapshot building process takes precedence.
82+
validateFunction(callback, 'callback');
83+
if (deserializeMainIsSet) {
84+
throw new ERR_DUPLICATE_STARTUP_SNAPSHOT_MAIN_FUNCTION();
85+
}
86+
deserializeMainIsSet = true;
87+
88+
_setDeserializeMainFunction(function deserializeMain() {
89+
const {
90+
prepareMainThreadExecution
91+
} = require('internal/bootstrap/pre_execution');
92+
93+
// This should be in sync with run_main_module.js until we make that
94+
// a built-in main function.
95+
prepareMainThreadExecution(true);
96+
markBootstrapComplete();
97+
callback(data);
98+
});
99+
}
100+
101+
module.exports = {
102+
initializeCallbacks,
103+
runDeserializeCallbacks,
104+
// Exposed to require('v8').startupSnapshot
105+
namespace: {
106+
addDeserializeCallback,
107+
addSerializeCallback,
108+
setDeserializeMainFunction,
109+
isBuildingSnapshot
110+
}
111+
};

‎lib/v8.js

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ const {
4040
Serializer,
4141
Deserializer
4242
} = internalBinding('serdes');
43+
const {
44+
namespace: startupSnapshot
45+
} = require('internal/v8/startup_snapshot');
4346

4447
let profiler = {};
4548
if (internalBinding('config').hasInspector) {
@@ -372,4 +375,5 @@ module.exports = {
372375
serialize,
373376
writeHeapSnapshot,
374377
promiseHooks,
378+
startupSnapshot
375379
};

‎src/api/embed_helpers.cc

+13
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "debug_utils-inl.h"
44

55
using v8::Context;
6+
using v8::Function;
67
using v8::Global;
78
using v8::HandleScope;
89
using v8::Isolate;
@@ -44,6 +45,13 @@ Maybe<int> SpinEventLoop(Environment* env) {
4445
if (EmitProcessBeforeExit(env).IsNothing())
4546
break;
4647

48+
{
49+
HandleScope handle_scope(isolate);
50+
if (env->RunSnapshotSerializeCallback().IsEmpty()) {
51+
break;
52+
}
53+
}
54+
4755
// Emit `beforeExit` if the loop became alive either after emitting
4856
// event, or after running some callbacks.
4957
more = uv_loop_alive(env->event_loop());
@@ -54,6 +62,11 @@ Maybe<int> SpinEventLoop(Environment* env) {
5462
if (env->is_stopping()) return Nothing<int>();
5563

5664
env->set_trace_sync_io(false);
65+
// Clear the serialize callback even though the JS-land queue should
66+
// be empty this point so that the deserialized instance won't
67+
// attempt to call into JS again.
68+
env->set_snapshot_serialize_callback(Local<Function>());
69+
5770
env->PrintInfoForSnapshotIfDebug();
5871
env->VerifyNoStrongBaseObjects();
5972
return EmitProcessExit(env);

‎src/env.cc

+21
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ using v8::Array;
3434
using v8::Boolean;
3535
using v8::Context;
3636
using v8::EmbedderGraph;
37+
using v8::EscapableHandleScope;
3738
using v8::Function;
3839
using v8::FunctionTemplate;
3940
using v8::HandleScope;
@@ -671,6 +672,26 @@ void Environment::PrintSyncTrace() const {
671672
isolate(), stack_trace_limit(), StackTrace::kDetailed));
672673
}
673674

675+
MaybeLocal<Value> Environment::RunSnapshotSerializeCallback() const {
676+
EscapableHandleScope handle_scope(isolate());
677+
if (!snapshot_serialize_callback().IsEmpty()) {
678+
Context::Scope context_scope(context());
679+
return handle_scope.EscapeMaybe(snapshot_serialize_callback()->Call(
680+
context(), v8::Undefined(isolate()), 0, nullptr));
681+
}
682+
return handle_scope.Escape(Undefined(isolate()));
683+
}
684+
685+
MaybeLocal<Value> Environment::RunSnapshotDeserializeMain() const {
686+
EscapableHandleScope handle_scope(isolate());
687+
if (!snapshot_deserialize_main().IsEmpty()) {
688+
Context::Scope context_scope(context());
689+
return handle_scope.EscapeMaybe(snapshot_deserialize_main()->Call(
690+
context(), v8::Undefined(isolate()), 0, nullptr));
691+
}
692+
return handle_scope.Escape(Undefined(isolate()));
693+
}
694+
674695
void Environment::RunCleanup() {
675696
started_cleanup_ = true;
676697
TRACE_EVENT0(TRACING_CATEGORY_NODE1(environment), "RunCleanup");

‎src/env.h

+7
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,9 @@ class NoArrayBufferZeroFillScope {
558558
V(promise_hook_handler, v8::Function) \
559559
V(promise_reject_callback, v8::Function) \
560560
V(script_data_constructor_function, v8::Function) \
561+
V(snapshot_serialize_callback, v8::Function) \
562+
V(snapshot_deserialize_callback, v8::Function) \
563+
V(snapshot_deserialize_main, v8::Function) \
561564
V(source_map_cache_getter, v8::Function) \
562565
V(tick_callback_function, v8::Function) \
563566
V(timers_callback_function, v8::Function) \
@@ -1333,6 +1336,10 @@ class Environment : public MemoryRetainer {
13331336

13341337
void RunWeakRefCleanup();
13351338

1339+
v8::MaybeLocal<v8::Value> RunSnapshotSerializeCallback() const;
1340+
v8::MaybeLocal<v8::Value> RunSnapshotDeserializeCallback() const;
1341+
v8::MaybeLocal<v8::Value> RunSnapshotDeserializeMain() const;
1342+
13361343
// Strings and private symbols are shared across shared contexts
13371344
// The getters simply proxy to the per-isolate primitive.
13381345
#define VP(PropertyName, StringValue) V(v8::Private, PropertyName)

‎src/node.cc

+8
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,14 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) {
485485
return scope.EscapeMaybe(cb(info));
486486
}
487487

488+
// TODO(joyeecheung): move these conditions into JS land and let the
489+
// deserialize main function take precedence. For workers, we need to
490+
// move the pre-execution part into a different file that can be
491+
// reused when dealing with user-defined main functions.
492+
if (!env->snapshot_deserialize_main().IsEmpty()) {
493+
return env->RunSnapshotDeserializeMain();
494+
}
495+
488496
if (env->worker_context() != nullptr) {
489497
return StartExecution(env, "internal/main/worker_thread");
490498
}

‎src/node_snapshotable.cc

+37-14
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ void SerializeBindingData(Environment* env,
457457

458458
namespace mksnapshot {
459459

460-
static void CompileSnapshotMain(const FunctionCallbackInfo<Value>& args) {
460+
void CompileSerializeMain(const FunctionCallbackInfo<Value>& args) {
461461
CHECK(args[0]->IsString());
462462
Local<String> filename = args[0].As<String>();
463463
Local<String> source = args[1].As<String>();
@@ -485,23 +485,46 @@ static void CompileSnapshotMain(const FunctionCallbackInfo<Value>& args) {
485485
}
486486
}
487487

488-
static void Initialize(Local<Object> target,
489-
Local<Value> unused,
490-
Local<Context> context,
491-
void* priv) {
488+
void SetSerializeCallback(const FunctionCallbackInfo<Value>& args) {
489+
Environment* env = Environment::GetCurrent(args);
490+
CHECK(env->snapshot_serialize_callback().IsEmpty());
491+
CHECK(args[0]->IsFunction());
492+
env->set_snapshot_serialize_callback(args[0].As<Function>());
493+
}
494+
495+
void SetDeserializeCallback(const FunctionCallbackInfo<Value>& args) {
496+
Environment* env = Environment::GetCurrent(args);
497+
CHECK(env->snapshot_deserialize_callback().IsEmpty());
498+
CHECK(args[0]->IsFunction());
499+
env->set_snapshot_deserialize_callback(args[0].As<Function>());
500+
}
501+
502+
void SetDeserializeMainFunction(const FunctionCallbackInfo<Value>& args) {
503+
Environment* env = Environment::GetCurrent(args);
504+
CHECK(env->snapshot_deserialize_main().IsEmpty());
505+
CHECK(args[0]->IsFunction());
506+
env->set_snapshot_deserialize_main(args[0].As<Function>());
507+
}
508+
509+
void Initialize(Local<Object> target,
510+
Local<Value> unused,
511+
Local<Context> context,
512+
void* priv) {
492513
Environment* env = Environment::GetCurrent(context);
493-
Isolate* isolate = context->GetIsolate();
494-
env->SetMethod(target, "compileSnapshotMain", CompileSnapshotMain);
495-
target
496-
->Set(context,
497-
FIXED_ONE_BYTE_STRING(isolate, "cleanups"),
498-
v8::Array::New(isolate))
499-
.Check();
514+
env->SetMethod(target, "compileSerializeMain", CompileSerializeMain);
515+
env->SetMethod(target, "markBootstrapComplete", MarkBootstrapComplete);
516+
env->SetMethod(target, "setSerializeCallback", SetSerializeCallback);
517+
env->SetMethod(target, "setDeserializeCallback", SetDeserializeCallback);
518+
env->SetMethod(
519+
target, "setDeserializeMainFunction", SetDeserializeMainFunction);
500520
}
501521

502-
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
503-
registry->Register(CompileSnapshotMain);
522+
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
523+
registry->Register(CompileSerializeMain);
504524
registry->Register(MarkBootstrapComplete);
525+
registry->Register(SetSerializeCallback);
526+
registry->Register(SetDeserializeCallback);
527+
registry->Register(SetDeserializeMainFunction);
505528
}
506529
} // namespace mksnapshot
507530
} // namespace node
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
const fs = require('fs');
4+
const zlib = require('zlib');
5+
const path = require('path');
6+
const assert = require('assert');
7+
8+
const {
9+
isBuildingSnapshot,
10+
addSerializeCallback,
11+
addDeserializeCallback,
12+
setDeserializeMainFunction
13+
} = require('v8').startupSnapshot;
14+
15+
const filePath = path.resolve(__dirname, '../x1024.txt');
16+
const storage = {};
17+
18+
assert(isBuildingSnapshot());
19+
20+
addSerializeCallback(({ filePath }) => {
21+
console.error('serializing', filePath);
22+
storage[filePath] = zlib.gzipSync(fs.readFileSync(filePath));
23+
}, { filePath });
24+
25+
addDeserializeCallback(({ filePath }) => {
26+
console.error('deserializing', filePath);
27+
storage[filePath] = zlib.gunzipSync(storage[filePath]);
28+
}, { filePath });
29+
30+
setDeserializeMainFunction(({ filePath }) => {
31+
console.log(storage[filePath].toString());
32+
}, { filePath });

‎test/parallel/test-bootstrap-modules.js

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const expectedModules = new Set([
2121
'Internal Binding fs_event_wrap',
2222
'Internal Binding fs',
2323
'Internal Binding heap_utils',
24+
'Internal Binding mksnapshot',
2425
'Internal Binding messaging',
2526
'Internal Binding module_wrap',
2627
'Internal Binding native_module',
@@ -167,6 +168,7 @@ const expectedModules = new Set([
167168
'NativeModule url',
168169
'NativeModule util',
169170
'NativeModule v8',
171+
'NativeModule internal/v8/startup_snapshot',
170172
'NativeModule vm',
171173
]);
172174

0 commit comments

Comments
 (0)
Please sign in to comment.