From 3561514ff5df15efdf76f4fc7f92a034d94ad168 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 27 May 2021 20:51:46 +0800 Subject: [PATCH] bootstrap: implement --snapshot-blob and --build-snapshot This patch introduces `--build-snapshot` and `--snapshot-blob` options for creating and using user land snapshots. For the initial iteration, user land CJS modules and ESM are not yet supported in the snapshot, so only one single file can be snapshotted (users can bundle their applications into a single script with their bundler of choice to build a snapshot though). A subset of builtins should already work, and support for more builtins are being added. This PR includes tests checking that the TypeScript compiler and the marked markdown renderer (and the builtins they use) can be snapshotted and deserialized. To generate a snapshot using `snapshot.js` as entry point and write the snapshot blob to `snapshot.blob`: ``` $ echo "globalThis.foo = 'I am from the snapshot'" > snapshot.js $ node --snapshot-blob snapshot.blob --build-snapshot snapshot.js ``` To restore application state from `snapshot.blob`, with `index.js` as the entry point script for the deserialized application: ``` $ echo "console.log(globalThis.foo)" > index.js $ node --snapshot-blob snapshot.blob index.js I am from the snapshot ``` Users can also use the `v8.startupSnapshot` API to specify an entry point at snapshot building time, thus avoiding the need of an additional entry script at deserialization time: ``` $ echo "require('v8').startupSnapshot.setDeserializeMainFunction(() => console.log('I am from the snapshot'))" > snapshot.js $ node --snapshot-blob snapshot.blob --build-snapshot snapshot.js $ node --snapshot-blob snapshot.blob I am from the snapshot ``` Note that this patch only adds functionality to the `node` executable for building run-time user-land snapshots, the generated snapshot is stored into a separate file on disk. Building a single binary with both Node.js and an embedded snapshot has already been possible with the `--node-snapshot-main` option to the `configure` script if the user compiles Node.js from source. It would be a different task to enable the `node` executable to produce a single binary that contains both Node.js and an embedded snapshot without building Node.js from source, which should be layered on top of the SEA (Single Executable Apps) initiative. Known limitations/bugs that are being fixed in the upstream: - V8 hits a DCHECK when deserializing certain mutated globals, e.g. `Error.stackTraceLimit` (it should work fine in the release build, however): https://chromium-review.googlesource.com/c/v8/v8/+/3319481 - Layout of V8's read-only heap can be inconsistent after deserialization, resulting in memory corruption: https://bugs.chromium.org/p/v8/issues/detail?id=12921 PR-URL: https://github.com/nodejs/node/pull/38905 Refs: https://github.com/nodejs/node/issues/35711 Reviewed-By: Chengzhong Wu Reviewed-By: Matteo Collina --- doc/api/cli.md | 76 ++ src/env.cc | 36 +- src/env.h | 11 +- src/node.cc | 133 +++- src/node_internals.h | 19 + src/node_options.cc | 7 + src/node_options.h | 1 + src/node_snapshotable.cc | 723 +++++++++++++++++- test/fixtures/snapshot/check-mutate-fs.js | 6 + .../fixtures/snapshot/decompress-gzip-sync.js | 20 + test/fixtures/snapshot/mutate-fs.js | 5 + test/parallel/test-snapshot-api.js | 49 ++ test/parallel/test-snapshot-basic.js | 112 +++ test/parallel/test-snapshot-cjs-main.js | 53 ++ test/parallel/test-snapshot-error.js | 73 ++ test/parallel/test-snapshot-eval.js | 73 ++ test/parallel/test-snapshot-gzip.js | 63 ++ 17 files changed, 1408 insertions(+), 52 deletions(-) create mode 100644 test/fixtures/snapshot/check-mutate-fs.js create mode 100644 test/fixtures/snapshot/decompress-gzip-sync.js create mode 100644 test/fixtures/snapshot/mutate-fs.js create mode 100644 test/parallel/test-snapshot-api.js create mode 100644 test/parallel/test-snapshot-basic.js create mode 100644 test/parallel/test-snapshot-cjs-main.js create mode 100644 test/parallel/test-snapshot-error.js create mode 100644 test/parallel/test-snapshot-eval.js create mode 100644 test/parallel/test-snapshot-gzip.js diff --git a/doc/api/cli.md b/doc/api/cli.md index 27a539e6de8819..af1ca0bfe64887 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -100,6 +100,62 @@ If this flag is passed, the behavior can still be set to not abort through [`process.setUncaughtExceptionCaptureCallback()`][] (and through usage of the `node:domain` module that uses it). +### `--build-snapshot` + + + +> Stability: 1 - Experimental + +Generates a snapshot blob when the process exits and writes it to +disk, which can be loaded later with `--snapshot-blob`. + +When building the snapshot, if `--snapshot-blob` is not specified, +the generated blob will be written, by default, to `snapshot.blob` +in the current working directory. Otherwise it will be written to +the path specified by `--snapshot-blob`. + +```console +$ echo "globalThis.foo = 'I am from the snapshot'" > snapshot.js + +# Run snapshot.js to intialize the application and snapshot the +# state of it into snapshot.blob. +$ node --snapshot-blob snapshot.blob --build-snapshot snapshot.js + +$ echo "console.log(globalThis.foo)" > index.js + +# Load the generated snapshot and start the application from index.js. +$ node --snapshot-blob snapshot.blob index.js +I am from the snapshot +``` + +The [`v8.startupSnapshot` API][] can be used to specify an entry point at +snapshot building time, thus avoiding the need of an additional entry +script at deserialization time: + +```console +$ echo "require('v8').startupSnapshot.setDeserializeMainFunction(() => console.log('I am from the snapshot'))" > snapshot.js +$ node --snapshot-blob snapshot.blob --build-snapshot snapshot.js +$ node --snapshot-blob snapshot.blob +I am from the snapshot +``` + +For more information, check out the [`v8.startupSnapshot` API][] documentation. + +Currently the support for run-time snapshot is experimental in that: + +1. User-land modules are not yet supported in the snapshot, so only + one single file can be snapshotted. Users can bundle their applications + into a single script with their bundler of choice before building + a snapshot, however. +2. Only a subset of the built-in modules work in the snapshot, though the + Node.js core test suite checks that a few fairly complex applications + can be snapshotted. Support for more modules are being added. If any + crashes or buggy behaviors occur when building a snapshot, please file + a report in the [Node.js issue tracker][] and link to it in the + [tracking issue for user-land snapshots][]. + ### `--completion-bash` + +> Stability: 1 - Experimental + +When used with `--build-snapshot`, `--snapshot-blob` specifies the path +where the generated snapshot blob will be written to. If not specified, +the generated blob will be written, by default, to `snapshot.blob` +in the current working directory. + +When used without `--build-snapshot`, `--snapshot-blob` specifies the +path to the blob that will be used to restore the application state. + ### `--test`