Repository navigation
examples: ask the guest for its snapshot, and keep a VM whose spawn is unconfirmed (SL5.1a follow-up) - #1508
Merged
Pedro Henrique Penna (ppenna) merged 2 commits intoNov 12, 2026
Conversation
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The review identifies no active issues in the host-workflow example updates.
No open issues 🎉
What changed in this PR
Aligns NVX’s compile-only host workflow examples with the public API’s snapshot and process-lifecycle contracts, following up on SL5.1a without changing the library.
Changes:
- Connects to the verified VM endpoint and invokes a caller-provided capture request before waiting for snapshot completion.
- Wraps unconfirmed spawn failures in
Unstopped, allowing for a missing process handle, and documents retention of VM directories, restore views, and snapshot locks.
| File | Description |
|---|---|
nvx-rs/examples/host-workflows.rs |
Adds request-driven capture and shared handling of unconfirmed spawn failures, with updated resource-retention guidance. |
Review effort: Balanced
host-workflows' capture spawned the capture's VM and waited for its exit, saying that the guest captures itself, as the guest example shows. But the guest example's init captures once the host asked for a snapshot and the init answered the request, as the guest-agent guidelines have every init do, and nvx-init, NVX's own init, waits in a capture boot for NVXC's capture request before it captures. Neither captured without a request, so the capture's VM ran until the deadline and was stopped without a snapshot. capture now takes a request callback, as restore takes activate. Once the VM runs, capture connects to its control endpoint, which only the VM's own process may serve, attaches with the capability, and has the callback ask the guest for its snapshot in the caller's own protocol; only then does it wait for the VM's exit. A failed connection, attachment or request ends the VM through end, as an expired deadline does. The CLI's snapshot capture drives NVX's own capture in the same order: it connects to the VM's verified endpoint, attaches, sends the capture request of ControlSession::capture, which the library keeps private, and waits for the exit. The doc now names what the configuration needs, a boot with a capture, rather than a boot that asks for one. Part of #1118 (SL5.1a follow-up). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
vm::spawn ends the process that it started when it fails after creating it, but when it cannot confirm that exit it fails with ErrorKind::StillRunning, and the process may still run. host-workflows' restore said that a failed spawn stops its child, and returned that error with ?, so it neither ended the VM nor returned an Unstopped error, the two outcomes that its doc gives the caller. A caller that followed the doc could release the snapshot's lock, or remove the view, while OpenVMM still used them, although vm::create requires the lock through the restore-ready event or the VM's verified exit. capture did the same with its VM directory. capture and restore now start their VM through start, which turns a spawn's StillRunning into an Unstopped error without the VM. Unstopped holds an Option<Launched>, None when the spawn kept no process, which the caller can then neither stop nor wait for: it keeps the VM's directory, and a restore's view and lock, until the host restarts. The CLI tells the same case apart: its foreground runs keep their temporary VM directory after a spawn's StillRunning, and a sandbox start that restores leaves its VM to the launch journal's recovery, which ends it before the snapshot's lock goes, through means that the library doesn't make public. cold_boot keeps returning vm::spawn's error: it promises no end of its VM and no Unstopped error, and the error's kind tells its caller that the VM may still run. Part of #1118 (SL5.1a follow-up). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Pedro Henrique Penna (ppenna)
force-pushed
the
agents/slim-sl5-1a-examples
branch
from
November 12, 2026 02:49
82ebf5d to
23289a7
Compare
Pedro Henrique Penna (ppenna)
marked this pull request as ready for review
November 12, 2026 02:49
Pedro Henrique Penna (ppenna)
merged commit Nov 12, 2026
99d2022
into
feature/nvx-refactor
83 of 86 checks passed
Pedro Henrique Penna (ppenna)
deleted the
agents/slim-sl5-1a-examples
branch
November 12, 2026 03:09
This was referenced Nov 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1118 (SL5.1a follow-up). Parent #1086, umbrella #1076.
What and why
SL5.1a (#1488) added the compile-only examples
nvx-rs/examples/host-workflows.rsandguest-workflows.rs, which CI compiles and nothing runs. Copilot's review of #1500 raised two findings onhost-workflows.rs, in code that is now onfeature/nvx-refactor: r4544426592 and r4544426638. Both hold against the library's contracts. This PR fixes them in the example alone, which stays compile-only and uses the public API alone.1.
capture()never asked for the captureSymptom.
capture()spawned the capture's VM and waited for its exit, saying "the guest captures itself, as the guest example shows". No guest that the examples describe captures without a request, so the capture's VM ran until the deadline and was stopped without a snapshot.Evidence.
guest-workflows.rs'scapture"Captures the VM once the host asked for a snapshot and the init answered the request".doc/guest-agent-guidelines.md, "Keep the transport silent": "The init answers the host's capture request, then sends nothing on its transport untilcapturereturns". The rustdoc ofguest::snapshotsays the same.nvx_snapshot_tier=platform) reportsNVX-INIT-CAPTURE: waitingand serves its control session (guest/init.rs,AgentConfig::serve_capture), whose dispatcher queues a capture only forAPP_CAPTURE(guest/managed_agent/dispatch.rs).Boot::capturesays "what a capture of the VM adds, if the VM may be captured"; the guest's ownguest::snapshot::capturerequests the capture.snapshot capture(host/cli/snapshots/capture.rs,request_and_wait) connects to the VM's verified endpoint, attaches with the capability, sends the request with the crate-privateControlSession::capture, and only then waits for the VM's exit.Fix.
capture()takes arequest: FnOnce(&mut ControlSession, Instant) -> Result<(), SessionError>callback, asrestore()takesactivate: the caller asks its guest for the snapshot in its own protocol, since NVXC's capture request isn't public. Once the VM runs,capture()connects withsession::connect, which accepts only an endpoint that the VM's own process serves, attaches withControlSession::attach, calls the callback, and closes the session, as the CLI does; then it waits for the exit. A failed connection, attachment or request ends the VM throughend(), as an expired deadline does. The doc names what the configuration needs, a boot with acapture, rather than "a boot that asks for a capture". It also says that a failed request or an expired deadline finishes no snapshot, where it said that the VM captures nothing, since a guest that got the request may already capture (from the code review).2. A spawn that couldn't confirm its exit escaped
UnstoppedSymptom.
restore()said "A spawn that fails stops the child that it started" and returnedvm::spawn's error with?. When the spawn could not confirm the exit of the process that it started, that error neither ended the VM nor came as anUnstoppederror, the two outcomes thatrestore()'s doc gives the caller. A caller that followed the doc could release the snapshot's lock, or remove the view, while OpenVMM still used them.capture()did the same with its VM directory.Evidence.
vm::spawndocuments: "A failure after child creation terminates only that child and waits for its confirmed exit. ... An unconfirmed exit has kindErrorKind::StillRunning, with the original error." It fails so when the cleanup of a child whose identity it cannot verify fails (spawn_verified_with_cleanup), or when Windows creates the process without a handle (platform/windows/spawn.rs), and it keeps no handle to the process either way.vm::createrequires the caller's snapshot lock "through a peer-verified restore-ready event or the VM's verified exit".StillRunning(host/cli/run.rs,host/cli/sandbox/lifecycle/start.rs), and a sandbox start that restores leaves its VM to the launch journal's recovery, which ends it before the snapshot's lock goes (launch_locked, tested bya_restore_whose_spawn_cannot_confirm_that_its_vm_ended_recovers_it_before_its_snapshot_goes_free). That recovery finds the VM through its endpoint and its log, by means that the library doesn't make public.Fix.
capture()andrestore()start their VM through a newstart(), which turns a spawn'sStillRunninginto anUnstoppederror without the VM.Unstopped::launchedis now anOption<Launched>,Nonewhen the spawn kept no process, which the caller can then neither stop nor wait for: it keeps the VM's directory, and a restore's view and lock of the snapshot, until the host restarts. The comment that said that a failed spawn stops its child is gone, andrestore()'s doc says that the call ends the VM when the restore fails, or fails with anUnstoppederror, after which the caller keeps holding the lock.cold_boot()doesn't change: it returnsvm::spawn's ownnvx::Error, promises noUnstoppederror, holds no lock or view, and does nothing fallible after its spawn, so the error's kind,StillRunning, tells its caller that the VM may still run.Size
One file,
nvx-rs/examples/host-workflows.rs: +49 −18 lines (+33 −8 of code, +15 −10 of comments). Examples count as documentation for the size rules, so 0 core lines in 0 counted files.Commits
examples: ask the guest for its snapshot before a capture waitsexamples: keep a VM whose spawn could not confirm its exitNot in this PR
A test that runs either example or a VM; any change to the library or its public API (the snapshots don't change);
guest-workflows.rs, whose capture already follows the host's request; andexternal-launch.rs, which launches through its own launcher rather thanvm::spawn.Validation
On the head
82ebf5d63df(base64ad98afacc), with this branch'scheck-nvx-rscommands, innvx-rs/, withNVX_BUNDLE=skipand Rust 1.95.0:cargo fmt --all --checkcargo clippy --locked --all-targets --all-features -- -D warningscargo clippy --locked --example host-workflows --features host -- -D warningsgit rebase --exec)cargo clippy --locked --target x86_64-unknown-linux-musl --no-default-features --features guest --all-targets -- -D warnings, the Clippy run ofnvx.py check-guest-binaries, which compilesguest-workflowscargo build --locked --example host-workflows --features hostcargo +1.89 check --locked --all-targets --all-featurescargo check --locked --all-targets --all-features --target aarch64-apple-darwinpython scripts/check_test_layout.py,python scripts/check_markdown_links.pypython scripts/check_test_changes.py --base origin/feature/nvx-refactorlocal_gates.py(dev's copy,--files nvx-rs/examples/host-workflows.rs) also selects gates that this change cannot affect, which I leave to CI: the Test step ofcheck-nvx-rs, whosecargo testbuilds the examples as the build above does; artifact bundling; rustdoc, which documents the library and not the examples; the public API check, with no library change; the guest binaries' build and musl tests, with no guest change; the runtime adapter's checks; and the Linux CLI validation. No VM ran: both examples stay compile-only.The case matrix,
doc/acceptance-case-matrix.md, needs no change: it lists tests rather than examples, its rows that name the examples as coverage stay true, and no test changes.Nor do the README and the docs that link the examples.
nvx-rs/README.mdlists the host example's capture without saying how it starts, and its sections onnvx::guest::snapshotandsnapshot capturealready have the guest answer the host's capture request, asdoc/guest-agent-guidelines.mddoes. No rustdoc links the examples.