| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent d92b013 commit 5dec186
9 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,6 +6,10 @@ | |||
| 6 | 6 | added: | |
| 7 | 7 | - v19.7.0 | |
| 8 | 8 | - v18.16.0 | |
| 9 | + changes: | ||
| 10 | + - version: REPLACEME | ||
| 11 | + pr-url: https://github.com/nodejs/node/pull/46824 | ||
| 12 | + description: Added support for "useSnapshot". | ||
| 9 | 13 | --> | |
| 10 | 14 | ||
| 11 | 15 | > Stability: 1 - Experimental: This feature is being designed and will change. | |
@@ -169,14 +173,46 @@ The configuration currently reads the following top-level fields: | |||
| 169 | 173 | { | |
| 170 | 174 | "main": "/path/to/bundled/script.js", | |
| 171 | 175 | "output": "/path/to/write/the/generated/blob.blob", | |
| 172 | - "disableExperimentalSEAWarning": true // Default: false | ||
| 176 | + "disableExperimentalSEAWarning": true, // Default: false | ||
| 177 | + "useSnapshot": false // Default: false | ||
| 173 | 178 | } | |
| 174 | 179 | ``` | |
| 175 | 180 | ||
| 176 | 181 | If the paths are not absolute, Node.js will use the path relative to the | |
| 177 | 182 | current working directory. The version of the Node.js binary used to produce | |
| 178 | 183 | the blob must be the same as the one to which the blob will be injected. | |
| 179 | 184 | ||
| 185 | + ### Startup snapshot support | ||
| 186 | + | ||
| 187 | + The `useSnapshot` field can be used to enable startup snapshot support. In this | ||
| 188 | + case the `main` script would not be when the final executable is launched. | ||
| 189 | + Instead, it would be run when the single executable application preparation | ||
| 190 | + blob is generated on the building machine. The generated preparation blob would | ||
| 191 | + then include a snapshot capturing the states initialized by the `main` script. | ||
| 192 | + The final executable with the preparation blob injected would deserialize | ||
| 193 | + the snapshot at run time. | ||
| 194 | + | ||
| 195 | + When `useSnapshot` is true, the main script must invoke the | ||
| 196 | + [`v8.startupSnapshot.setDeserializeMainFunction()`][] API to configure code | ||
| 197 | + that needs to be run when the final executable is launched by the users. | ||
| 198 | + | ||
| 199 | + The typical pattern for an application to use snapshot in a single executable | ||
| 200 | + application is: | ||
| 201 | + | ||
| 202 | + 1. At build time, on the building machine, the main script is run to | ||
| 203 | + initialize the heap to a state that's ready to take user input. The script | ||
| 204 | + should also configure a main function with | ||
| 205 | + [`v8.startupSnapshot.setDeserializeMainFunction()`][]. This function will be | ||
| 206 | + compiled and serialized into the snapshot, but not invoked at build time. | ||
| 207 | + 2. At run time, the main function will be run on top of the deserialized heap | ||
| 208 | + on the user machine to process user input and generate output. | ||
| 209 | + | ||
| 210 | + The general constraints of the startup snapshot scripts also apply to the main | ||
| 211 | + script when it's used to build snapshot for the single executable application, | ||
| 212 | + and the main script can use the [`v8.startupSnapshot` API][] to adapt to | ||
| 213 | + these constraints. See | ||
| 214 | + [documentation about startup snapshot support in Node.js][]. | ||
| 215 | + | ||
| 180 | 216 | ## Notes | |
| 181 | 217 | ||
| 182 | 218 | ### `require(id)` in the injected module is not file based | |
@@ -249,6 +285,9 @@ to help us document them. | |||
| 249 | 285 | [`process.execPath`]: process.md#processexecpath | |
| 250 | 286 | [`require()`]: modules.md#requireid | |
| 251 | 287 | [`require.main`]: modules.md#accessing-the-main-module | |
| 288 | + [`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data | ||
| 289 | + [`v8.startupSnapshot` API]: v8.md#startup-snapshot-api | ||
| 290 | + [documentation about startup snapshot support in Node.js]: cli.md#--build-snapshot | ||
| 252 | 291 | [fuse]: https://www.electronjs.org/docs/latest/tutorial/fuses | |
| 253 | 292 | [postject]: https://github.com/nodejs/postject | |
| 254 | 293 | [signtool]: https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -16,6 +16,10 @@ const { | |||
| 16 | 16 | anonymousMainPath, | |
| 17 | 17 | } = internalBinding('mksnapshot'); | |
| 18 | 18 | ||
| 19 | + const { isExperimentalSeaWarningNeeded } = internalBinding('sea'); | ||
| 20 | + | ||
| 21 | + const { emitExperimentalWarning } = require('internal/util'); | ||
| 22 | + | ||
| 19 | 23 | const { | |
| 20 | 24 | getOptionValue, | |
| 21 | 25 | } = require('internal/options'); | |
@@ -126,6 +130,7 @@ function requireForUserSnapshot(id) { | |||
| 126 | 130 | return require(normalizedId); | |
| 127 | 131 | } | |
| 128 | 132 | ||
| 133 | + | ||
| 129 | 134 | function main() { | |
| 130 | 135 | prepareMainThreadExecution(true, false); | |
| 131 | 136 | initializeCallbacks(); | |
@@ -167,6 +172,10 @@ function main() { | |||
| 167 | 172 | ||
| 168 | 173 | const serializeMainArgs = [process, requireForUserSnapshot, minimalRunCjs]; | |
| 169 | 174 | ||
| 175 | + if (isExperimentalSeaWarningNeeded()) { | ||
| 176 | + emitExperimentalWarning('Single executable application'); | ||
| 177 | + } | ||
| 178 | + | ||
| 170 | 179 | if (getOptionValue('--inspect-brk')) { | |
| 171 | 180 | internalBinding('inspector').callAndPauseOnStart( | |
| 172 | 181 | runEmbedderEntryPoint, undefined, ...serializeMainArgs); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -175,7 +175,7 @@ function patchProcessObject(expandArgv1) { | |||
| 175 | 175 | __proto__: null, | |
| 176 | 176 | enumerable: true, | |
| 177 | 177 | // Only set it to true during snapshot building. | |
| 178 | - configurable: getOptionValue('--build-snapshot'), | ||
| 178 | + configurable: isBuildingSnapshot(), | ||
| 179 | 179 | value: process.argv[0], | |
| 180 | 180 | }); | |
| 181 | 181 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -292,6 +292,17 @@ MaybeLocal<Value> StartExecution(Environment* env, StartExecutionCallback cb) { | |||
| 292 | 292 | ||
| 293 | 293 | CHECK(!env->isolate_data()->is_building_snapshot()); | |
| 294 | 294 | ||
| 295 | + #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION | ||
| 296 | + if (sea::IsSingleExecutable()) { | ||
| 297 | + sea::SeaResource sea = sea::FindSingleExecutableResource(); | ||
| 298 | + // The SEA preparation blob building process should already enforce this, | ||
| 299 | + // this check is just here to guard against the unlikely case where | ||
| 300 | + // the SEA preparation blob has been manually modified by someone. | ||
| 301 | + CHECK_IMPLIES(sea.use_snapshot(), | ||
| 302 | + !env->snapshot_deserialize_main().IsEmpty()); | ||
| 303 | + } | ||
| 304 | + #endif | ||
| 305 | + | ||
| 295 | 306 | // TODO(joyeecheung): move these conditions into JS land and let the | |
| 296 | 307 | // deserialize main function take precedence. For workers, we need to | |
| 297 | 308 | // move the pre-execution part into a different file that can be | |
@@ -1198,49 +1209,66 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr, | |||
| 1198 | 1209 | return exit_code; | |
| 1199 | 1210 | } | |
| 1200 | 1211 | ||
| 1201 | - ExitCode LoadSnapshotDataAndRun(const SnapshotData** snapshot_data_ptr, | ||
| 1202 | - const InitializationResultImpl* result) { | ||
| 1203 | - ExitCode exit_code = result->exit_code_enum(); | ||
| 1212 | + bool LoadSnapshotData(const SnapshotData** snapshot_data_ptr) { | ||
| 1204 | 1213 | // nullptr indicates there's no snapshot data. | |
| 1205 | 1214 | DCHECK_NULL(*snapshot_data_ptr); | |
| 1215 | + | ||
| 1216 | + bool is_sea = false; | ||
| 1217 | + #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION | ||
| 1218 | + if (sea::IsSingleExecutable()) { | ||
| 1219 | + is_sea = true; | ||
| 1220 | + sea::SeaResource sea = sea::FindSingleExecutableResource(); | ||
| 1221 | + if (sea.use_snapshot()) { | ||
| 1222 | + std::unique_ptr<SnapshotData> read_data = | ||
| 1223 | + std::make_unique<SnapshotData>(); | ||
| 1224 | + std::string_view snapshot = sea.main_code_or_snapshot; | ||
| 1225 | + if (SnapshotData::FromBlob(read_data.get(), snapshot)) { | ||
| 1226 | + *snapshot_data_ptr = read_data.release(); | ||
| 1227 | + return true; | ||
| 1228 | + } else { | ||
| 1229 | + fprintf(stderr, "Invalid snapshot data in single executable binary\n"); | ||
| 1230 | + return false; | ||
| 1231 | + } | ||
| 1232 | + } | ||
| 1233 | + } | ||
| 1234 | + #endif | ||
| 1235 | + | ||
| 1206 | 1236 | // --snapshot-blob indicates that we are reading a customized snapshot. | |
| 1207 | - if (!per_process::cli_options->snapshot_blob.empty()) { | ||
| 1237 | + // Ignore it when we are loading from SEA. | ||
| 1238 | + if (!is_sea && !per_process::cli_options->snapshot_blob.empty()) { | ||
| 1208 | 1239 | std::string filename = per_process::cli_options->snapshot_blob; | |
| 1209 | 1240 | FILE* fp = fopen(filename.c_str(), "rb"); | |
| 1210 | 1241 | if (fp == nullptr) { | |
| 1211 | 1242 | fprintf(stderr, "Cannot open %s", filename.c_str()); | |
| 1212 | - exit_code = ExitCode::kStartupSnapshotFailure; | ||
| 1213 | - return exit_code; | ||
| 1243 | + return false; | ||
| 1214 | 1244 | } | |
| 1215 | 1245 | std::unique_ptr<SnapshotData> read_data = std::make_unique<SnapshotData>(); | |
| 1216 | 1246 | bool ok = SnapshotData::FromFile(read_data.get(), fp); | |
| 1217 | 1247 | fclose(fp); | |
| 1218 | 1248 | if (!ok) { | |
| 1219 | - // If we fail to read the customized snapshot, | ||
| 1220 | - // simply exit with kStartupSnapshotFailure. | ||
| 1221 | - exit_code = ExitCode::kStartupSnapshotFailure; | ||
| 1222 | - return exit_code; | ||
| 1249 | + return false; | ||
| 1223 | 1250 | } | |
| 1224 | 1251 | *snapshot_data_ptr = read_data.release(); | |
| 1225 | - } else if (per_process::cli_options->node_snapshot) { | ||
| 1226 | - // If --snapshot-blob is not specified, we are reading the embedded | ||
| 1227 | - // snapshot, but we will skip it if --no-node-snapshot is specified. | ||
| 1252 | + return true; | ||
| 1253 | + } | ||
| 1254 | + | ||
| 1255 | + if (per_process::cli_options->node_snapshot) { | ||
| 1256 | + // If --snapshot-blob is not specified or if the SEA contains no snapshot, | ||
| 1257 | + // we are reading the embedded snapshot, but we will skip it if | ||
| 1258 | + // --no-node-snapshot is specified. | ||
| 1228 | 1259 | const node::SnapshotData* read_data = | |
| 1229 | 1260 | SnapshotBuilder::GetEmbeddedSnapshotData(); | |
| 1230 | - if (read_data != nullptr && read_data->Check()) { | ||
| 1261 | + if (read_data != nullptr) { | ||
| 1262 | + if (!read_data->Check()) { | ||
| 1263 | + return false; | ||
| 1264 | + } | ||
| 1231 | 1265 | // If we fail to read the embedded snapshot, treat it as if Node.js | |
| 1232 | 1266 | // was built without one. | |
| 1233 | 1267 | *snapshot_data_ptr = read_data; | |
| 1234 | 1268 | } | |
| 1235 | 1269 | } | |
| 1236 | 1270 | ||
| 1237 | - NodeMainInstance main_instance(*snapshot_data_ptr, | ||
| 1238 | - uv_default_loop(), | ||
| 1239 | - per_process::v8_platform.Platform(), | ||
| 1240 | - result->args(), | ||
| 1241 | - result->exec_args()); | ||
| 1242 | - exit_code = main_instance.Run(); | ||
| 1243 | - return exit_code; | ||
| 1271 | + return true; | ||
| 1244 | 1272 | } | |
| 1245 | 1273 | ||
| 1246 | 1274 | static ExitCode StartInternal(int argc, char** argv) { | |
@@ -1275,7 +1303,8 @@ static ExitCode StartInternal(int argc, char** argv) { | |||
| 1275 | 1303 | ||
| 1276 | 1304 | std::string sea_config = per_process::cli_options->experimental_sea_config; | |
| 1277 | 1305 | if (!sea_config.empty()) { | |
| 1278 | - return sea::BuildSingleExecutableBlob(sea_config); | ||
| 1306 | + return sea::BuildSingleExecutableBlob( | ||
| 1307 | + sea_config, result->args(), result->exec_args()); | ||
| 1279 | 1308 | } | |
| 1280 | 1309 | ||
| 1281 | 1310 | // --build-snapshot indicates that we are in snapshot building mode. | |
@@ -1290,7 +1319,15 @@ static ExitCode StartInternal(int argc, char** argv) { | |||
| 1290 | 1319 | } | |
| 1291 | 1320 | ||
| 1292 | 1321 | // Without --build-snapshot, we are in snapshot loading mode. | |
| 1293 | - return LoadSnapshotDataAndRun(&snapshot_data, result.get()); | ||
| 1322 | + if (!LoadSnapshotData(&snapshot_data)) { | ||
| 1323 | + return ExitCode::kStartupSnapshotFailure; | ||
| 1324 | + } | ||
| 1325 | + NodeMainInstance main_instance(snapshot_data, | ||
| 1326 | + uv_default_loop(), | ||
| 1327 | + per_process::v8_platform.Platform(), | ||
| 1328 | + result->args(), | ||
| 1329 | + result->exec_args()); | ||
| 1330 | + return main_instance.Run(); | ||
| 1294 | 1331 | } | |
| 1295 | 1332 | ||
| 1296 | 1333 | int Start(int argc, char** argv) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -92,12 +92,16 @@ void NodeMainInstance::Run(ExitCode* exit_code, Environment* env) { | |||
| 92 | 92 | bool runs_sea_code = false; | |
| 93 | 93 | #ifndef DISABLE_SINGLE_EXECUTABLE_APPLICATION | |
| 94 | 94 | if (sea::IsSingleExecutable()) { | |
| 95 | - runs_sea_code = true; | ||
| 96 | 95 | sea::SeaResource sea = sea::FindSingleExecutableResource(); | |
| 97 | - std::string_view code = sea.code; | ||
| 98 | - LoadEnvironment(env, code); | ||
| 96 | + if (!sea.use_snapshot()) { | ||
| 97 | + runs_sea_code = true; | ||
| 98 | + std::string_view code = sea.main_code_or_snapshot; | ||
| 99 | + LoadEnvironment(env, code); | ||
| 100 | + } | ||
| 99 | 101 | } | |
| 100 | 102 | #endif | |
| 103 | + // Either there is already a snapshot main function from SEA, or it's not | ||
| 104 | + // a SEA at all. | ||
| 101 | 105 | if (!runs_sea_code) { | |
| 102 | 106 | LoadEnvironment(env, StartExecutionCallback{}); | |
| 103 | 107 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments