Repository navigation
nvx-rs: attribute a clone's failures, record them, and reattach it - #951
Pedro Henrique Penna (ppenna) merged 12 commits into
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Lease release can remove the retry-blocking failure marker before revoking the reservation, allowing another launch under the same lease if cleanup fails.
3 open issues
What changed in this PR
This PR extends NVX’s clone restore lifecycle with attributed failures, persistent failure and teardown records, and reattachment after the caller restarts. It implements the failure-handling and recovery portion of #844; real-VM integration remains for the next PR.
Changes:
- Adds typed failure attribution across clone planning, launch, activation, and commit.
- Records clone progress and teardown outcomes, with cleanup when the lease is released.
- Adds clone reattachment and fake-process tests for failures, recovery, and restore-ready connections.
| File | Description |
|---|---|
| nvx-rs/src/host/template/store/leases/tests.rs | Tests release cleanup of progress and failure records. |
| nvx-rs/src/host/template/store/leases.rs | Documents clone records removed during release. |
| nvx-rs/src/host/template/store.rs | Exposes directory validation for failure attribution. |
| nvx-rs/src/host/template/mod.rs | Exports and documents failure and reattachment APIs. |
| nvx-rs/src/host/template/clone/tests/restore.rs | Tests attribution, teardown records, and caller-reported failures. |
| nvx-rs/src/host/template/clone/tests.rs | Tests planning failures and their evidence. |
| nvx-rs/src/host/template/clone/record.rs | Attributes activation-record construction errors. |
| nvx-rs/src/host/template/clone/reattach.rs | Implements clone reattachment and failure recovery. |
| nvx-rs/src/host/template/clone/launch.rs | Tracks launch progress and records failures. |
| nvx-rs/src/host/supervisor/tests.rs | Adapts tests to reattachment reporting callbacks. |
| nvx-rs/src/host/supervisor/mod.rs | Adds callbacks before teardown and runtime-record removal. |
| nvx-rs/src/host/store/tests.rs | Extends clone-file cleanup coverage. |
| nvx-rs/src/host/store/mod.rs | Registers progress and failure files with the sandbox store. |
| nvx-rs/src/host/store/lifecycle/tests.rs | Tests removal and validation of the new records. |
| nvx-rs/src/host/sandbox/scratch.rs | Exposes binding version for attribution checks. |
| nvx-rs/src/host/sandbox/distro.rs | Exposes binding version for attribution checks. |
| nvx-rs/src/host/platform/windows.rs | Tests restore-ready clients that disconnect before acceptance. |
| nvx-rs/src/host/platform/linux.rs | Tests restore-ready clients that disconnect before acceptance. |
| nvx-rs/README.md | Documents failure attribution, records, teardown, and reattachment. |
Review effort: Balanced
281d8d0 to
2c8d550
Compare
ad46acb to
0d3bdbf
Compare
There was a problem hiding this comment.
🟡 Changes recommended
An unreadable failure record is treated as absent, allowing a clone with unresolved teardown to become usable again under the same lease instead of preserving the error or safely terminating the VM.
1 open issue
3 resolved since last review
Review effort: Balanced
2c8d550 to
7ff6e1f
Compare
0d3bdbf to
2e5e5be
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The reattach wildcard branch discards clone-failure.json read errors and records a generic run-stage exit failure, losing diagnostics that should be preserved at Stage::Reattach.
1 open issue
1 resolved since last review
Review effort: Balanced
40ee8e4 to
a4893fd
Compare
2e5e5be to
49e0abb
Compare
There was a problem hiding this comment.
🔵 Needs a closer look
Cross-platform VMM termination and crash-recovery changes need human review because tests use fakes and real clone restore/reattach validation is deferred to PR 5.
No open issues 🎉
1 resolved since last review
Review effort: Balanced
a4893fd to
933dc80
Compare
49e0abb to
57f8610
Compare
There was a problem hiding this comment.
🟡 Changes recommended
The spawn state is not durably recorded before Restoring is returned, so early teardown can cause reattachment to skip failure reporting and incorrectly report a failed guest repair as Ended.
1 open issue
Review effort: Balanced
933dc80 to
a53a0fa
Compare
A clone's plan, launch, and restore sequence now fail with a CloneFailure, whose Evidence attributes the failure to an Outcome: the request, the host's staging once the caller proves it, the host, unknown, the template, which alone quarantines the leased generation through its tombstone, or the repair stage. OpenVMM's outcome report gives the status with which the guest ended the clone's VM, which prevails over what the host saw: a status of the repair at any point, and one of the activation before the start was sent. A VMM that exits before its restore-ready event leaves OpenVMM's fatal error in its log, whose time ABI code may attribute the failure to a host that cannot present the template's CPU or time; the wait for the event gives a VMM that closed the endpoint without the event ten seconds to exit. From the launch on, a failure ends the clone's VMM through its verified identity, and the sandbox records it in clone-failure.json, before the teardown, with the teardown pending, and again once the teardown ended, with its stage, evidence, status, original outcome, transaction, generation, progress, and teardown. The clone's progress log, clone-progress.log, whose first line names the launch and the host's boot, records each step from the spawn to the commit without a sync, in one write for each step from the restore-ready event on. A teardown that is unresolved keeps the launch's journal or runtime record, and with it the lease, and a clone whose sandbox records a failure launches no more under its lease. A release removes both files, and a cold launch writes neither. reattach_clone takes a clone over once the caller of its launch restarted: it checks a committed clone in a new session, with the byte-equal Activate of clone-activation.bin, which the clone answers with its cached Ready; ends a clone of another runtime identity, or one that fails the check, through its verified identity; records the failure of a launch that a crash interrupted, and of a VM that exited, before it ends the launch or forgets the VM; classifies a VM that exited by one reading of its outcome report; and finishes a failure that a crash left pending, or resolves the teardown of an earlier one. A progress log that is damaged, missing, or written during another boot leaves how far the clone got uncertain, so that a status that a workload may have given is no evidence of the activation. It reads nothing of the template store, so a tombstone or a retirement of the generation leaves the clone its lease. CloneRecords::read reads what a sandbox keeps of a clone. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A step of the caller's own between a clone's Ready and the commit of its launch, such as one that maps a directory of the host into the clone, may fail. Activated::fail and Started::fail fail the clone with the caller's evidence, at the start and the commit stages, through the recorded teardown that the restore sequence's own failures take: the failure is recorded before the teardown and once it ended. Abandoning a launch records nothing, as before. A status of the guest's that is evidence prevails over what the host or the caller saw of the same end, but no longer over evidence of the template, a check of the generation or a diagnosis that attributes a defect to it, which shows the provenance that the status alone leaves to establish. The record holds the status beside it, for a failure's record, the finish of a pending one, and the resolution of an unresolved one alike. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A launch whose VMM was spawned, but whose identity it could not record in its journal, recorded the clone's failure as if no VMM had been spawned: the launch then ends the VMM through its handle, and removes the journal, so nothing else tells it. The failure now records the spawn, and Progress::Spawned says that the journal records the VMM unless the launch could not record it. The documentation of reattach_clone says that its bound applies to a VMM whose launch committed: a launch that a crash interrupted ends as LockedStore::recover ends it, within the wait that the store's recovery allows the VMM's termination. Evidence::Protocol says, as the restore sequence now does, that the bytes that it refuses at the restore-ready endpoint are those that differ from the event. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A reattach took a record of the clone's failure that it could not read for none, so a clone that had failed came back live if its VM still ran and answered, ended without a failure if its VM had exited, and indeterminate if its VM could not be examined. The record still tells that the clone launched, and failed: the reattach now fails such a clone at its own stage, with the error of the record's read, the host's evidence, once it ended a VMM of the clone that runs through its verified identity, or with the teardown unresolved for one that it cannot examine, which a later reattach resolves. The failure that it records replaces the record that cannot be read. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A reattach that found a record of the clone's failure that it could not read failed the clone with the error of the read only where it would otherwise have kept the clone, found it ended, or found its VM indeterminate. Elsewhere the error was lost: before it ended a launch that a crash of its caller interrupted, or cleared the runtime record of a VM that exited with a status of the guest's that is evidence, or without a report of the guest's own end, the reattach recorded a new failure in place of the record, at the stage of the clone's progress, as it did for a VM that runs under another runtime identity. The reattach now takes such a record as the record of an earlier failure that cannot tell how the clone failed. If the store holds the journal or the runtime record of the clone's launch, which the reattach may remove, it first replaces the record with its own failure at its own stage, with the error of the record's read and the host's evidence, its teardown pending, which keeps how far the clone got for a reattach after a crash: before the restore-ready event, only the journal holds the spawn. It then ends a VMM of the clone that still runs through its verified identity, and records the failure with the teardown that it found or made, over whose evidence a status of the guest's in OpenVMM's outcome report prevails. Part of #844. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
…launch returns A clone's launch noted the spawn of its VMM without writing it in the progress log, which recorded it with the next step, since the launch's journal records the spawn too. A launch that its caller abandoned, or dropped, before the clone's activation removed the journal as it ended, and nothing recorded the spawn any more: a later reattach then took the clone for one whose VMM never spawned, read no outcome report of its VM, and found it ended, even when the guest had ended the VM with a status that is evidence of a failure, such as one of its repair. The launch now appends the spawn to the progress log before it returns. A launch that cannot write it fails at the launch stage, which ends its VMM and records the failure, as the restore sequence does when it cannot record a step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
…e read A reattach accepted any failure record that it could read in the sandbox as the failure of the clone that the sandbox's binding leases, whatever the template store, lease, generation, key, and launch that the record names. A record of another clone's failure, misplaced in the sandbox, was then returned as this clone's, and a failure that it attributed to the template could quarantine a generation that this clone does not lease. The reattach, and the read of a clone's records, now read the failure record only as the failure of the binding's clone: a record that names another store, lease, generation, key, or launch fails to read, as a malformed record does, naming what differs. The reattach takes it as a record that it cannot read: it replaces it with its own failure at the reattach stage, with the host's evidence, so the failure that it returns, and records, is this clone's. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
On Linux, a clone's launch listens for the restore-ready event of its VMM at a Unix socket in the sandbox's directory, which the bind creates with the mode that the process's umask leaves. A restrictive umask, such as 0200, could withhold even its owner's permission to write it, and a VMM that is not root cannot connect to a socket without that permission: the launch would then fail as if the VMM had sent nothing. The launch now gives the socket mode 0600 right after the bind, before it spawns the VMM. It opens the socket's path only to locate it, following no link, refuses anything but a socket, and sets the mode through the descriptor's link in /proc, as the store sets the modes of the directories that it creates. The review of #949, which added the restore-ready socket, found this. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
On Linux, an acquisition refused a generation whose payload was not a directory of the current user with mode 0700, but checked the payload by its path, which follows links. The release of the lease opens the payload through the template store's directory instead, following no link at the store's directory, at the directory of the generations, or at the payload. A link put in one of those places once the store was opened thus let the acquisition grant a lease that its release could not end until the link was gone. The acquisition now opens the payload as the release does, through the store's directory, whose identity it reads there, and checks the payload's owner and mode through the payload's handle. Before it writes anything, it refuses a payload that it cannot open that way, one that is gone or whose store's directory holds another store, and one that is not such a directory. The review of #949, which made the release open the payload through a handle, found this. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A holder that checks whether its VM's VMM has exited, then stops the VM, attributes to the stop a VMM that exits in between: Vmm::stop finds it exited, runs no step, reports StopOutcome::Graceful, and clears the runtime record of its launch. A clone's reattach then finds no record, and ends the clone as one that its holder stopped, rather than with the loss of its status. Vmm::stop_unless_exited stops the VM as Vmm::stop does, unless its own check, before the graceful step, finds the VMM exited. It then returns None, runs no step, signals nothing, and keeps the record, so that reattach_clone finds that the VM exited unheld, records the loss of the clone's status if no report of the guest's own end tells why, and only then clears the record. Once that check found the VMM running, its exit is the stop's, as before. Vmm::stop, Vmm::terminate, and their callers are unchanged, and so are the rules of quarantine and of leases: the loss of a status quarantines nothing, and a lease is released only once its VM's exit is verified. Tests cover both stops through stand-in VMMs, and a clone whose VMM exits once its holder found it running, before the holder's stop begins: the clone fails at its run with the loss of its status. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
A clone's failure is recorded with its teardown pending before the teardown, so that a crash of the caller during the teardown leaves the record for a reattach to finish. But a pending record that could not be written, as on a full disk, was ignored: the teardown still removed the launch's journal, or the VM's runtime record, and a crash before the final record then left nothing of the failure. A reattach found a clone that ended, the lease was released as if nothing had failed, and the failure was lost, a diagnosis of the template's included. A clone's lifecycle record, the journal of its launch or the runtime record of its VM, and with it the lease, now outlives every failure that cannot be recorded before its teardown: - A failure of the restore sequence, or of the caller's Activated::fail or Started::fail, ends the clone's VMM through its handle, and keeps the launch's journal, through Starting::end_keeping_journal, as a failed commit does. Its teardown is unresolved until a reattach ends the launch, which records the failure, though not how it happened. - A launch that fails before it returns kills a VMM that it spawned and could not record, as before, and keeps its journal for recovery: the `failing` callback of supervisor::launch_reporting now returns whether the caller recorded the failure. - reattach_clone fails before it ends or forgets anything of the clone's VM: the VM and its records stay as it found them, running or not, for a later reattach. The `exited` callback of supervisor::reattach_reporting now fails the reattach, and keeps the runtime record, if the caller cannot record the VM's exit. - A record that a write put in place, but whose sync of the sandbox's directory failed, may not be durable yet, and a reattach takes it as the record of an earlier failure, which it does not write again. So reattach_clone syncs the sandbox's directory before it ends, or forgets, anything of a launch whose failure the sandbox records, and fails as above if the sync fails. The rules of quarantine and of leases are unchanged: a lease is released only once its VM's exit is verified, and a failure that a caller still holds quarantines as before. Tests make the record of the failure unwritable with a directory in its place: a caller's diagnosis of the template keeps the launch's journal, then a reattach finds the clone failed; reattaches of an interrupted launch, of a VM that exited, of one under another runtime identity, and of one that fails its activation check end and forget nothing, then record the failure once they can; and the supervisor keeps the journal of a launch, and the runtime record of a VM that exited, whose caller cannot record them. Another refuses the sync of an earlier failure's record: the reattach then ends nothing, and one that can sync ends the launch only once it synced. Reviews of this pull request found these. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a424884c-018a-45f5-a7fe-c57548a873f0
0352bb9 to
1abc278
Compare
There was a problem hiding this comment.
🟡 Changes recommended
A critical recovery bug can leave clone progress at Launching after a caller crash, causing reattach_clone to return Ended even for repair status 195 because the guard incorrectly requires a recorded spawn.
1 open issue
Review effort: Balanced
A reattach read OpenVMM's outcome report of a clone's VM only once the clone's progress showed the spawn of its VMM, which the launch's journal records, and its progress log once the launch returns. A launch whose caller crashed after the spawn, before either recorded it, is one that a recovery of the sandbox, as supervisor::reattach runs it, ends once the VMM has exited, removing the journal: nothing recorded the spawn any more, and a later reattach_clone found the clone ended at its step launching, even when the guest had ended the VM with a status that is evidence of a failure, such as one of its repair, and recorded no failure. The launch removes the report of an earlier VM of the sandbox before it creates the clone's progress log, and CloneRecords::read takes a report as the clone's VM's from then on. The reattach now does too: exit_fault attributes the report's status once the clone got past its plan, ReattachedClone::Ended returns the report from that point, and a reattach that cannot read the record of a failure takes the teardown of a VM that left a report as an exit, whose status then prevails, as it does once a record keeps the spawn. Tests crash a launch once it began, write the report of the VMM that its journal does not record, and let a recovery of the sandbox end the launch: a reattach then fails the clone with the evidence and the status of the guest's repair, or finds it ended with the report of its VM, and one that cannot read the record of a failure takes the repair's status too. The case of a launch that never began no longer writes a report, which no VM of the clone could have left. The review of this pull request found this. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🔵 Needs a closer look
Human assessment is needed because recovery can overlook a valid activation record, causing a guest repair failure to go unrecorded and the clone to be reported as ended.
No open issues 🎉
1 resolved since last review
Review effort: Balanced


Stacked on #950; this PR retargets when its parent merges.
Chain: #888 → #949 → #950 → this PR, on
dev; #879, #881, #885, #886, #877, #932, #884, #887, #933, #880, #936, #937, #939, #940, and #941 have merged.Part of #844 (PR 4 of 5: failures, teardown, and reattach).
What and why
#950 runs a clone's restore sequence to its sandbox's start and the launch's commit. A failure there ends the launch with an error that says what failed, but not whose failure it is, and nothing of it outlives the caller. This PR attributes every failure of a clone, from its plan to its commit, records it in the clone's sandbox with the teardown that followed, and lets a caller that restarted take its clones over.
Attribution
A clone's plan, launch, and restore sequence fail with a
CloneFailure, whoseEvidenceattributes the failure to anOutcome, at aStage, once the clone got as far as itsProgress:CloneFailure::with_staging_proven;CloneFailure::quarantine;OpenVMM's outcome report gives the status with which the guest ended the clone's VM, which
status_evidenceattributes: a status of the repair at any point, and one of the activation before the start was sent. It prevails over what the host or the caller saw of the same end, but not over evidence of the template, which shows the provenance that a status alone leaves to establish.A VMM that exits before its restore-ready event, as OpenVMM does once it cannot restore the snapshot, leaves its fatal error in its log. The failure's message keeps that error, and its time ABI code attributes the failure to the host when it says that the host cannot present the template's CPU profile, TSC, or LAPIC timer. The wait for the event gives a VMM that closed the endpoint without sending it ten seconds to exit.
Records and teardown
From the launch on, a failure ends the clone's VMM through its verified identity. The sandbox keeps two more files, which a release removes, as a removal of the sandbox does, and which a cold launch never writes:
clone-progress.log, the clone's progress log: a first line that names the launch and the boot of the host, then a line for each step from the spawn to the commit, without a sync. The launch writes the spawn before it returns, since a teardown of the launch removes its journal, which records the spawn too. It removes an earlier launch's log through a handle of the sandbox's directory, which its lock checked, as a release removes the clone's files, and creates its own log where none may exist: a path that names another sandbox's directory by then fails the launch, rather than lose that sandbox's log;clone-failure.json, the record of the failure, JSON in formatFAILURE_FORMAT. It is written before the teardown, with the teardown pending, so that a crash of the caller during the teardown leaves it for a reattach to finish, and again once the teardown ended, each time durably: the write syncs the sandbox's directory once the record is in place. It holds the stage, the evidence and the status, the original outcome, the transaction, the generation, the progress, and the teardown: no VM, an exit or a termination that the host verified, or an unresolved one. It keeps at most 8192 characters of the failure's message, and 1024 of the reason of an unresolved teardown: of a longer text, its first and last ones, with an ellipsis between them. So a reattach reads every record that a failure writes, however long a caller's diagnosis.An unresolved teardown keeps the launch's journal or runtime record, and with it the lease. So does a failure whose pending record cannot be written, as on a full disk: nothing that tells that the clone launched is removed before its failure is recorded. A failure of the restore sequence, or of the caller's, then ends the clone's VMM through its handle and keeps the launch's journal, Starting::end_keeping_journal, with its teardown unresolved; a launch that fails before it returns keeps its journal for recovery; and a reattach fails before it ends or forgets anything of the clone's VM, which it leaves as it found it. A record that a write put in place, but whose sync failed, may not be durable yet, so a reattach that finds it syncs the sandbox's directory before it ends or forgets anything of the clone's launch, and fails the same way if that sync fails. A crash of the caller before the record then leaves a launch that a reattach records as failed, rather than a clone that ended. A clone whose sandbox records a failure launches no more under its lease. A release ends the reservation of the lock that acquired the lease before it removes anything (#949), so one that fails after it removed the failure record leaves no launch to begin. A launch that spawned its VMM but could not record it in its journal records the failure as of the spawn, and then ends the VMM through its handle.
A step of the caller's own between Ready and the commit, such as one that maps a directory of the host into the clone, fails the clone with the caller's evidence through
Activated::failorStarted::fail. These record the failure and end the launch, as the sequence's own failures do.Reattach
reattach_clonetakes a clone over once the caller of its launch restarted:Activateofclone-activation.binwith the Ready that it answered once, and the reattach checks the transaction ID that it reports.ReattachedClone::Livereturns the VM with the session.LockedStore::recoverends it, within the store's own wait for the VMM's termination.CloneRecords::readreads what a sandbox keeps of a clone, for the operations that list, inspect, or remove clones. It fails for a failure record of another clone, as for one that cannot be read. It returns the outcome report of the clone's VM only once the clone got past its plan: the launch removes the report of an earlier VM of the sandbox, which the sandbox otherwise keeps until the next launch begins, before it creates the progress log.Stops
A holder that checks whether its clone's VMM has exited, then stops the VM, would attribute to its stop a VMM that exits in between:
Vmm::stopfinds it exited, runs no step, reportsStopOutcome::Graceful, and clears the runtime record, so a reattach finds no record, and ends the clone as one that its holder stopped, rather than with the loss of its status.Vmm::stop_unless_exitedstops the VM asVmm::stopdoes, unless its own check, before the graceful step, finds the VMM exited. It then returnsNone, runs no step, signals nothing, and keeps the runtime record, so thatreattach_clonefinds that the VM exited unheld, and records the loss of the clone's status, if no report of the guest's own end tells why, before it clears the record. Once that check found the VMM running, its exit is the stop's.Vmm::stop,Vmm::terminate, and their callers are unchanged.Also fixes
Two fixes to code from #949, which its review found:
Not in this PR
Validation
At this head, 0352bb9, with OpenVMM at d2338ea2, per commit: on Windows, fmt and Clippy with
-D warningsfor nvx-rs in CI's three configurations (--all-targets --all-features,--lib, and--lib --no-default-features),tests/host_modules.rs, the tests of the platform, template, store, sandbox, and supervisor modules, and the test adapter's fmt, Clippy, and host tests; and for the x86_64 Linux target, Clippy with-D warningsin the same three configurations, the aarch64-apple-darwin check, and the test adapter's Linux host and musl guest Clippy.The gates, every step of check-nvx-rs on Windows and on Linux x86_64 with the host tests as root on Linux, and the test adapter's VM suites,
vm_layered,vm_template,vm_restart, andvm_steps, on Windows (WHP) and Linux (MSHV), run at the final heads of the stack once its reviews are clean.Tests added, with the fake VMM and the fake control:
Vmm::stop_unless_exitedkeeps, and of running ones, which it stops asVmm::stopdoes; and a clone whose VMM exits once its holder found it running, before the holder's stop: it fails at its run with the loss of its status, which quarantines nothing, whileVmm::stopends it as stopped;These tests run no VM: clones' real restores come with PR 5's VM tests.