GeezerVison is showing github.com — v8: add v8.startupSnapshot utils · nodejs/node@513f524
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/errors.md +15Lines changed: 15 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1167,6 +1167,13 @@ because the `node:domain` module has been loaded at an earlier point in time. | 1167 | 1167 | The stack trace is extended to include the point in time at which the | 1168 | 1168 | `node:domain` module had been loaded. | 1169 | 1169 |
| | 1170 | +<a id="ERR_DUPLICATE_STARTUP_SNAPSHOT_MAIN_FUNCTION"></a> | | 1171 | + | | 1172 | +### `ERR_DUPLICATE_STARTUP_SNAPSHOT_MAIN_FUNCTION` | | 1173 | + | | 1174 | +[`v8.startupSnapshot.setDeserializeMainFunction()`][] could not be called | | 1175 | +because it had already been called before. | | 1176 | + | 1170 | 1177 | <a id="ERR_ENCODING_INVALID_ENCODED_DATA"></a> | 1171 | 1178 |
| 1172 | 1179 | ### `ERR_ENCODING_INVALID_ENCODED_DATA` | @@ -2293,6 +2300,13 @@ has occurred when attempting to start the loop. | 2293 | 2300 | Once no more items are left in the queue, the idle loop must be suspended. This | 2294 | 2301 | error indicates that the idle loop has failed to stop. | 2295 | 2302 |
| | 2303 | +<a id="ERR_NOT_BUILDING_SNAPSHOT"></a> | | 2304 | + | | 2305 | +### `ERR_NOT_BUILDING_SNAPSHOT` | | 2306 | + | | 2307 | +An attempt was made to use operations that can only be used when building | | 2308 | +V8 startup snapshot even though Node.js isn't building one. | | 2309 | + | 2296 | 2310 | <a id="ERR_NO_CRYPTO"></a> | 2297 | 2311 |
| 2298 | 2312 | ### `ERR_NO_CRYPTO` | @@ -3460,6 +3474,7 @@ The native call from `process.cpuUsage` could not be processed. | 3460 | 3474 | [`subprocess.send()`]: child_process.md#subprocesssendmessage-sendhandle-options-callback | 3461 | 3475 | [`util.getSystemErrorName(error.errno)`]: util.md#utilgetsystemerrornameerr | 3462 | 3476 | [`util.parseArgs()`]: util.md#utilparseargsconfig | | 3477 | +[`v8.startupSnapshot.setDeserializeMainFunction()`]: v8.md#v8startupsnapshotsetdeserializemainfunctioncallback-data | 3463 | 3478 | [`zlib`]: zlib.md | 3464 | 3479 | [crypto digest algorithm]: crypto.md#cryptogethashes | 3465 | 3480 | [debugger]: debugger.md | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/v8.md +131Lines changed: 131 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -853,6 +853,137 @@ Called immediately after a promise continuation executes. This may be after a | 853 | 853 | Called when the promise receives a resolution or rejection value. This may | 854 | 854 | occur synchronously in the case of `Promise.resolve()` or `Promise.reject()`. | 855 | 855 |
| | 856 | +## Startup Snapshot API | | 857 | + | | 858 | +<!-- YAML | | 859 | +added: REPLACEME | | 860 | +--> | | 861 | + | | 862 | +> Stability: 1 - Experimental | | 863 | +
| | 864 | +The `v8.startupSnapshot` interface can be used to add serialization and | | 865 | +deserialization hooks for custom startup snapshots. Currently the startup | | 866 | +snapshots can only be built into the Node.js binary from source. | | 867 | + | | 868 | +```console | | 869 | +$ cd /path/to/node | | 870 | +$ ./configure --node-snapshot-main=entry.js | | 871 | +$ make node | | 872 | +# This binary contains the result of the execution of entry.js | | 873 | +$ out/Release/node | | 874 | +``` | | 875 | + | | 876 | +In the example above, `entry.js` can use methods from the `v8.startupSnapshot` | | 877 | +interface to specify how to save information for custom objects in the snapshot | | 878 | +during serialization and how the information can be used to synchronize these | | 879 | +objects during deserialization of the snapshot. For example, if the `entry.js` | | 880 | +contains the following script: | | 881 | + | | 882 | +```cjs | | 883 | +'use strict'; | | 884 | + | | 885 | +const fs = require('fs'); | | 886 | +const zlib = require('zlib'); | | 887 | +const path = require('path'); | | 888 | +const assert = require('assert'); | | 889 | + | | 890 | +const { | | 891 | + isBuildingSnapshot, | | 892 | + addSerializeCallback, | | 893 | + addDeserializeCallback, | | 894 | + setDeserializeMainFunction | | 895 | +} = require('v8').startupSnapshot; | | 896 | + | | 897 | +const filePath = path.resolve(__dirname, '../x1024.txt'); | | 898 | +const storage = {}; | | 899 | + | | 900 | +assert(isBuildingSnapshot()); | | 901 | + | | 902 | +addSerializeCallback(({ filePath }) => { | | 903 | + storage[filePath] = zlib.gzipSync(fs.readFileSync(filePath)); | | 904 | +}, { filePath }); | | 905 | + | | 906 | +addDeserializeCallback(({ filePath }) => { | | 907 | + storage[filePath] = zlib.gunzipSync(storage[filePath]); | | 908 | +}, { filePath }); | | 909 | + | | 910 | +setDeserializeMainFunction(({ filePath }) => { | | 911 | + console.log(storage[filePath].toString()); | | 912 | +}, { filePath }); | | 913 | +``` | | 914 | + | | 915 | +The resulted binary will simply print the data deserialized from the snapshot | | 916 | +during start up: | | 917 | + | | 918 | +```console | | 919 | +$ out/Release/node | | 920 | +# Prints content of ./test/fixtures/x1024.txt | | 921 | +``` | | 922 | + | | 923 | +Currently the API is only available to a Node.js instance launched from the | | 924 | +default snapshot, that is, the application deserialized from a user-land | | 925 | +snapshot cannot use these APIs again. | | 926 | + | | 927 | +### `v8.startupSnapshot.addSerializeCallback(callback[, data])` | | 928 | + | | 929 | +<!-- YAML | | 930 | +added: REPLACEME | | 931 | +--> | | 932 | + | | 933 | +* `callback` {Function} Callback to be invoked before serialization. | | 934 | +* `data` {any} Optional data that will be passed to the `callback` when it | | 935 | + gets called. | | 936 | + | | 937 | +Add a callback that will be called when the Node.js instance is about to | | 938 | +get serialized into a snapshot and exit. This can be used to release | | 939 | +resources that should not or cannot be serialized or to convert user data | | 940 | +into a form more suitable for serialization. | | 941 | + | | 942 | +### `v8.startupSnapshot.addDeserializeCallback(callback[, data])` | | 943 | + | | 944 | +<!-- YAML | | 945 | +added: REPLACEME | | 946 | +--> | | 947 | + | | 948 | +* `callback` {Function} Callback to be invoked after the snapshot is | | 949 | + deserialized. | | 950 | +* `data` {any} Optional data that will be passed to the `callback` when it | | 951 | + gets called. | | 952 | + | | 953 | +Add a callback that will be called when the Node.js instance is deserialized | | 954 | +from a snapshot. The `callback` and the `data` (if provided) will be | | 955 | +serialized into the snapshot, they can be used to re-initialize the state | | 956 | +of the application or to re-acquire resources that the application needs | | 957 | +when the application is restarted from the snapshot. | | 958 | + | | 959 | +### `v8.startupSnapshot.setDeserializeMainFunction(callback[, data])` | | 960 | + | | 961 | +<!-- YAML | | 962 | +added: REPLACEME | | 963 | +--> | | 964 | + | | 965 | +* `callback` {Function} Callback to be invoked as the entry point after the | | 966 | + snapshot is deserialized. | | 967 | +* `data` {any} Optional data that will be passed to the `callback` when it | | 968 | + gets called. | | 969 | + | | 970 | +This sets the entry point of the Node.js application when it is deserialized | | 971 | +from a snapshot. This can be called only once in the snapshot building | | 972 | +script. If called, the deserialized application no longer needs an additional | | 973 | +entry point script to start up and will simply invoke the callback along with | | 974 | +the deserialized data (if provided), otherwise an entry point script still | | 975 | +needs to be provided to the deserialized application. | | 976 | + | | 977 | +### `v8.startupSnapshot.isBuildingSnapshot()` | | 978 | + | | 979 | +<!-- YAML | | 980 | +added: REPLACEME | | 981 | +--> | | 982 | + | | 983 | +* Returns: {boolean} | | 984 | + | | 985 | +Returns true if the Node.js instance is run to build a snapshot. | | 986 | + | 856 | 987 | [HTML structured clone algorithm]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm | 857 | 988 | [Hook Callbacks]: #hook-callbacks | 858 | 989 | [V8]: https://developers.google.com/v8/ | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/bootstrap/pre_execution.js +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -51,7 +51,6 @@ function prepareMainThreadExecution(expandArgv1 = false, | 51 | 51 | setupCoverageHooks(process.env.NODE_V8_COVERAGE); | 52 | 52 | } | 53 | 53 |
| 54 | | - | 55 | 54 | setupDebugEnv(); | 56 | 55 |
| 57 | 56 | // Print stack trace on `SIGINT` if option `--trace-sigint` presents. | @@ -82,6 +81,8 @@ function prepareMainThreadExecution(expandArgv1 = false, | 82 | 81 | initializeDeprecations(); | 83 | 82 | initializeWASI(); | 84 | 83 |
| | 84 | + require('internal/v8/startup_snapshot').runDeserializeCallbacks(); | | 85 | + | 85 | 86 | if (!initialzeModules) { | 86 | 87 | return; | 87 | 88 | } | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/bootstrap/switches/is_main_thread.js +19-4Lines changed: 19 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 |
| 3 | 3 | const { ObjectDefineProperty } = primordials; | 4 | 4 | const rawMethods = internalBinding('process_methods'); | 5 | | - | | 5 | +const { | | 6 | + addSerializeCallback, | | 7 | + isBuildingSnapshot | | 8 | +} = require('v8').startupSnapshot; | 6 | 9 | // TODO(joyeecheung): deprecate and remove these underscore methods | 7 | 10 | process._debugProcess = rawMethods._debugProcess; | 8 | 11 | process._debugEnd = rawMethods._debugEnd; | @@ -134,6 +137,12 @@ function refreshStderrOnSigWinch() { | 134 | 137 | stderr._refreshSize(); | 135 | 138 | } | 136 | 139 |
| | 140 | +function addCleanup(fn) { | | 141 | + if (isBuildingSnapshot()) { | | 142 | + addSerializeCallback(fn); | | 143 | + } | | 144 | +} | | 145 | + | 137 | 146 | function getStdout() { | 138 | 147 | if (stdout) return stdout; | 139 | 148 | stdout = createWritableStdioStream(1); | @@ -145,12 +154,14 @@ function getStdout() { | 145 | 154 | process.on('SIGWINCH', refreshStdoutOnSigWinch); | 146 | 155 | } | 147 | 156 |
| 148 | | - internalBinding('mksnapshot').cleanups.push(function cleanupStdout() { | | 157 | + addCleanup(function cleanupStdout() { | 149 | 158 | stdout._destroy = stdoutDestroy; | 150 | 159 | stdout.destroy(); | 151 | 160 | process.removeListener('SIGWINCH', refreshStdoutOnSigWinch); | 152 | 161 | stdout = undefined; | 153 | 162 | }); | | 163 | + // No need to add deserialize callback because stdout = undefined above | | 164 | + // causes the stream to be lazily initialized again later. | 154 | 165 | return stdout; | 155 | 166 | } | 156 | 167 |
| @@ -164,12 +175,14 @@ function getStderr() { | 164 | 175 | if (stderr.isTTY) { | 165 | 176 | process.on('SIGWINCH', refreshStderrOnSigWinch); | 166 | 177 | } | 167 | | - internalBinding('mksnapshot').cleanups.push(function cleanupStderr() { | | 178 | + addCleanup(function cleanupStderr() { | 168 | 179 | stderr._destroy = stderrDestroy; | 169 | 180 | stderr.destroy(); | 170 | 181 | process.removeListener('SIGWINCH', refreshStderrOnSigWinch); | 171 | 182 | stderr = undefined; | 172 | 183 | }); | | 184 | + // No need to add deserialize callback because stderr = undefined above | | 185 | + // causes the stream to be lazily initialized again later. | 173 | 186 | return stderr; | 174 | 187 | } | 175 | 188 |
| @@ -260,10 +273,12 @@ function getStdin() { | 260 | 273 | } | 261 | 274 | } | 262 | 275 |
| 263 | | - internalBinding('mksnapshot').cleanups.push(function cleanupStdin() { | | 276 | + addCleanup(function cleanupStdin() { | 264 | 277 | stdin.destroy(); | 265 | 278 | stdin = undefined; | 266 | 279 | }); | | 280 | + // No need to add deserialize callback because stdin = undefined above | | 281 | + // causes the stream to be lazily initialized again later. | 267 | 282 | return stdin; | 268 | 283 | } | 269 | 284 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/errors.js +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1001,6 +1001,8 @@ E('ERR_DOMAIN_CANNOT_SET_UNCAUGHT_EXCEPTION_CAPTURE', | 1001 | 1001 | 'The `domain` module is in use, which is mutually exclusive with calling ' + | 1002 | 1002 | 'process.setUncaughtExceptionCaptureCallback()', | 1003 | 1003 | Error); | | 1004 | +E('ERR_DUPLICATE_STARTUP_SNAPSHOT_MAIN_FUNCTION', | | 1005 | + 'Deserialize main function is already configured.', Error); | 1004 | 1006 | E('ERR_ENCODING_INVALID_ENCODED_DATA', function(encoding, ret) { | 1005 | 1007 | this.errno = ret; | 1006 | 1008 | return `The encoded data was not valid for encoding ${encoding}`; | @@ -1460,6 +1462,8 @@ E('ERR_NETWORK_IMPORT_BAD_RESPONSE', | 1460 | 1462 | "import '%s' received a bad response: %s", Error); | 1461 | 1463 | E('ERR_NETWORK_IMPORT_DISALLOWED', | 1462 | 1464 | "import of '%s' by %s is not supported: %s", Error); | | 1465 | +E('ERR_NOT_BUILDING_SNAPSHOT', | | 1466 | + 'Operation cannot be invoked when not building startup snapshot', Error); | 1463 | 1467 | E('ERR_NO_CRYPTO', | 1464 | 1468 | 'Node.js is not compiled with OpenSSL crypto support', Error); | 1465 | 1469 | E('ERR_NO_ICU', | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/main/mksnapshot.js +7-10Lines changed: 7 additions & 10 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 9 | 9 | const binding = internalBinding('mksnapshot'); | 10 | 10 | const { NativeModule } = require('internal/bootstrap/loaders'); | 11 | 11 | const { | 12 | | - compileSnapshotMain, | | 12 | + compileSerializeMain, | 13 | 13 | } = binding; | 14 | 14 |
| 15 | 15 | const { | @@ -83,7 +83,7 @@ const supportedModules = new SafeSet(new SafeArrayIterator([ | 83 | 83 | 'v8', | 84 | 84 | // 'vm', | 85 | 85 | // 'worker_threads', | 86 | | - // 'zlib', | | 86 | + 'zlib', | 87 | 87 | ])); | 88 | 88 |
| 89 | 89 | const warnedModules = new SafeSet(); | @@ -117,25 +117,22 @@ function main() { | 117 | 117 | } = require('internal/bootstrap/pre_execution'); | 118 | 118 |
| 119 | 119 | prepareMainThreadExecution(true, false); | 120 | | - process.once('beforeExit', function runCleanups() { | 121 | | - for (const cleanup of binding.cleanups) { | 122 | | - cleanup(); | 123 | | - } | 124 | | - }); | 125 | 120 |
| 126 | 121 | const file = process.argv[1]; | 127 | 122 | const path = require('path'); | 128 | 123 | const filename = path.resolve(file); | 129 | 124 | const dirname = path.dirname(filename); | 130 | 125 | 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(); | 132 | 129 |
| 133 | 130 | if (getOptionValue('--inspect-brk')) { | 134 | 131 | internalBinding('inspector').callAndPauseOnStart( | 135 | | - snapshotMainFunction, undefined, | | 132 | + serializeMainFunction, undefined, | 136 | 133 | requireForUserSnapshot, filename, dirname); | 137 | 134 | } else { | 138 | | - snapshotMainFunction(requireForUserSnapshot, filename, dirname); | | 135 | + serializeMainFunction(requireForUserSnapshot, filename, dirname); | 139 | 136 | } | 140 | 137 | } | 141 | 138 |
| |
|