Repository navigation
User-land snapshot JS API: request for feedback #42617
Description
Activity
- addedsnapshotIssues and PRs related to the startup snapshot.Issues and PRs related to the startup snapshot.
on Apr 6, 2022 cc @nodejs/startup
- changed the title
[-]User-land snapshot entry point API: request for feedback[/-][+]User-land snapshot JS API: request for feedback[/+]on Apr 6, 2022 This looks neat.
Should we distinguish the timing that the process is going to take the startup snapshot, and the timing that the snapshotting process is going to exit? With #42466, those two timings are the same: we can only get event
process#exit.Should we distinguish the timing that the process is going to take the startup snapshot
Sounds like a good idea, I wonder if
process.on('snapshot')would be good, or maybeprocess.on('serialize')in case we want to emit an event for deserialization too.Another alternative is to simply collect callbacks with a function (like
addSerializer()similar toaddDeserializer()in the OP) - the downside is that it might be less readable (though this is subjective), the upside is that the callbacks can't be tampered with throughprocess._eventsand we don't have to worry about AbortSignal integration etc. (we could consider adding event support later using these functions, though)I'd prefer a function in the
require('v8').snapshotnamespace thanprocessevents, for the reason you've stated.One point for the naming is that
addSerializersounds like it can add custom logic to serialize a JavaScript object, which is not how the mechanism works. The callback is called once before the serialization, not for each JavaScript object. So I'd find it would better be named asaddBeforeSerializeCallbackor something. The same applies toaddDeserializer.I understand the requirement and use case correctly, this will be useful. I use Native Messaging. I only need the
nodeexecutable in the binary download to run JavaScript locally - I don't need the rest of the folders and files in the download. I havn't found any means to just build thenodeexecutable in/binwithout the rest of the files created when Node.js is built. My use case is dynamically writing thenodeexecutable (potentially with the Native Messaging host code included in a single executable), using Native Messaging to meet requirements, then truncating thenodeexecutable to0when not used.The WIP is now functional, I have a test demonstrating the current APIs, in particular I added a
isBuildingSnapshot()method and use the namerequire('v8').startupSnapshotas the namespace (to avoid confusion with the heap snapshot and the WIP web snapshot).The
addSerializeCallbackis used in is_main_thread.js to clean up the std I/O streams so I didn't write a separate test for them (probably will add some later)'use strict'; const fs = require('fs'); const path = require('path'); const assert = require('assert'); const { isBuildingSnapshot, addDeserializeCallback, setDeserializeMainFunction } = require('v8').startupSnapshot; let deserializedKey; const storage = {}; function checkFileInSnapshot(storage) { assert(!isBuildingSnapshot()); const fixture = process.env.NODE_TEST_FIXTURE; const readFile = fs.readFileSync(fixture); console.log(`Read ${fixture} in deserialize main, length = ${readFile.byteLength}`); assert.deepStrictEqual(storage[deserializedKey], readFile); } if (isBuildingSnapshot()) { const fixture = path.join(__filename); const file = fs.readFileSync(fixture); console.log(`Read ${fixture} in snapshot main, length = ${file.byteLength}`); storage[fixture] = file; addDeserializeCallback((key) => { console.log('running deserialize callback'); deserializedKey = key; }, fixture); setDeserializeMainFunction( checkFileInSnapshot, storage ); }
$ ./configure --ninja --node-snapshot-main=test/fixtures/snapshot/v8-startup-snapshot-api.js $ ninja -C out/Release node ninja: Entering directory `out/Release' [1/4] ACTION node: node_mksnapshot_9b7a2d2290b02e76d66661df74749f56 Read /Users/joyee/projects/node/test/fixtures/snapshot/v8-startup-snapshot-api.js in snapshot main, length = 1009 [4/4] LINK node, POSTBUILDS $ NODE_TEST_FIXTURE=test/fixtures/snapshot/v8-startup-snapshot-api.js out/Release/node running deserialize callback Read test/fixtures/snapshot/v8-startup-snapshot-api.js in deserialize main, length = 1009Reacted by Chengzhong Wu, Béré Cyriac, William Chan, Steven, Guillermo Rauch, Rasmus Porsager and Daniel LandoReacted by William Chan, Guillermo Rauch, Steven, Caleb Boyd and Rasmus PorsagerReacted by Guillermo Rauch, Steven, Caleb Boyd, Rasmus Porsager, William Chan and Daniel Lando- added a commit that references this issue
on Jul 12, 2022 - added a commit that references this issue
on Jul 31, 2022 - added a commit that references this issue
on Oct 10, 2022 - added a commit that references this issue
on Mar 21, 2024
To improve the user experience of the user land snapshot, (quoting myself from #38905 (comment)) we need an API to specify user-land deserializers and deserialized main functions so that when starting up the application from the snapshot, the main script isn't necessary if the user already bakes a main function into the snapshot, with build-time snapshots (and maybe also run-time snapshots in later iterations) this could effectively turn the result into a single-file executable of an application.
I have a non-functional WIP here, my current idea is that in Node.js instances started in snapshot-building mode, we provide two hooks to the user, exposed by the
v8.snapshotnamespace, to specify user-land deserializers and the main script (ideas of better naming or better hooks are welcomed):addDeserializer(func, data): can be invoked multiple times in difference places where the user needs to serialize/synchronize something that's not pure JS (e.g. system resources), the deserializers added will be invoked when the snapshot is deserialized. The seconddataparameter passed into it will be passed into thefuncwhen it's invoked (similar to how we handle the timer callbacks)addDeserializerfromv8.snapshotto see if the application is run in snapshot building mode, so that they can add deserializers as needed.setDeserializedMainFunction(func, data): as the name implies this can only be invoked once, it throws an error on the second attempt. If the main function is set, the deserialized application does not need another entry point script specified from the command line when being started, instead the function specified will be invoked after all the deserializers are invoked.Example: assuming the following snippet is saved as
snapshot.js, the binary can be built withconfigure --node-snapshot-main=snapshot.jsat build time (requires compiling Node.js from source):With the run-time snapshot (at least the initial iteration), the snapshot blob would be written to disk (e.g.
node --build-snapshot snapshot.jscreates asnapshot.blobat the current working directory), so the user still needs to start the application with another snapshot blob file (e.g.node --snapshot-blob snapshot.blob arg1 arg2). In the next iteration though, we could create a copy of the binary and then append that blob to create a single-file executable, so that users can do this without having to compile Node.js from source.Refs: #35711