Skip to content

nvx-rs: activate a repaired clone through one Activate - #939

Open
Enrique Saurez (esaurez) wants to merge 1 commit into
esaurez/842-pr3-failure-statuses-20261024from
esaurez/843-pr1-activation-state-machine-20261024
Open

Enrique Saurez (esaurez) wants to merge 1 commit into
esaurez/842-pr3-failure-statuses-20261024from
esaurez/843-pr1-activation-state-machine-20261024

Conversation

@esaurez

@esaurez Enrique Saurez (esaurez) commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #937; this PR retargets when its parent merges.
Chain: #933 → #880 → #936 → #937 → this PR → #940 → #941, on dev; #879, #881, #885, #886, #877, #932, #884, and #887 have merged.

Part of #843 (PR 1 of 3: the activation state machine).

What and why

After a restore, a clone's guest repairs the clone behind its closed fence of workload admission (#842). Before it admits any workload, the clone must take its own configuration from the host, in one Activate, on a restricted control session. This adds nvx::guest::clone::Activation, the guest's state machine for that Activate. It has no I/O and depends on no framing: an adapter passes it what its control session receives, as an ActivationInput, and does what the returned ActivationAction says.

The states

ActivationState, in order:

  1. Transport bootstrap: the clone awaits the reset that the restore queued for the NVXS session that the guest attached before the capture, which the adapter acknowledges.
  2. Restricted: the session takes only flow control and one Activate. Anything else, a request or another reset, fails the activation. The activation decodes the record with nvx::activation::ActivationRecord::decode, and accepts only a clone's record, with a restore section, that expects the CPU count and the memory size that the repair reports (Repaired::cpus() and Repaired::memory()), before any side effect.
  3. Activating: the adapter applies the accepted record (ActivationAction::Apply), and reports the result to Activation::complete.
  4. Ready, with the mappings pending: complete with a success opens the caller's fence, the only call that opens it, and returns the ReadyActivation that Ready reports: that the activation applied, with the record's transaction ID. The host's mapping table and start follow in the adapter's order, as after a cold boot.
  5. Started: Activation::start records the adapter's start.

Retries, later sessions, and the deadline

  • A retry with the same transaction ID and a byte-equal record joins the running activation, or gets its cached result afterwards. That includes a later session, such as after the host reattaches, whose reset a ready clone takes, so the established activation is never applied again.
  • A changed record, another transaction, another reset before Ready, and a failed application are final: every later call returns the same ActivationFailure, which names its ActivationState and its ActivationCause.
  • The deadline, which the caller chooses, starts at the repair's acknowledgement of the restore (Repaired::acknowledged_at()), or, for a restore that the host did not gate, when the caller makes the activation. No input or retry extends it. The adapter wakes by Activation::deadline and calls Activation::tick, so the deadline holds however slowly input trickles in, and whether or not output drains.
  • The activation needs no privilege and makes no system call: of the clone, it changes only the caller's fence. It never powers the VM off. The adapter reports a failure and ends the VM, with the status of the failure's class from nvx::protocol::status.

The application itself comes in #940, and the adapter's framing in #941.

Later in the stack

#941 adds ActivationInput::TooLong(length): an Activate whose header announced length bytes, more than a record has, which the adapter's framing refused unread. It fails the activation as a request in the transport bootstrap, and from then on as a record that long, with ActivationError::TooLong. #941 also updates this PR's example: the application takes the activation's deadline, the agent queues the acknowledgement and Ready, and once the application returns, the agent passes the activation what the session received meanwhile before it completes the activation, so that a reset or a request that came before Ready fails the activation.

Validation

This PR was rebased onto #937's new head, 5683164, without conflicts and with its patch unchanged. At its new head, 62d8f3a, with the OpenVMM submodule at its gitlink, d2338ea2:

Linux x86_64:

  • nvx-rs: cargo fmt --check; Clippy with -D warnings, --all-targets --all-features, --lib, and --lib --no-default-features; cargo test --all-features (1791 passed), cargo test (877 passed), and the --lib --no-default-features build and test (260 passed); tests/host_modules.rs (22 passed); the bundled-artifacts test (19 passed); rustdoc with -D warnings; the MSRV (1.89) check; the aarch64-apple-darwin check.
  • Guest binaries: nvx.py check-guest-binaries (musl Clippy and tests, 973 passed), build-guest-binaries, test-guest-binaries (33 passed), check-static-elf.
  • tests/runtime-adapter, which builds on nvx-rs: fmt, Clippy, build, tests, and MSRV in its protocol-only (46 passed), host-only (98 passed), and musl guest (120 passed) configurations.
  • scripts/check_markdown_links.py.
  • As root, with standard input from /dev/null: the privileged capture test, the strict cold boot that stops at its violation, and the tests of guest::clone and guest::reseed (67 passed); the runtime adapter's library and guest-session tests (112 passed).

Windows (x86_64-pc-windows-msvc): nvx-rs fmt, Clippy (all features, --lib, no default features, each feature alone, and all features for the musl target), tests (all features 1118 passed, default 896, no default features 260), tests/host_modules.rs, rustdoc, MSRV; tests/runtime-adapter fmt, Clippy, build, tests (protocol-only 46 passed, host 99), and MSRV in its protocol-only and host configurations, and Clippy and MSRV in its musl guest configuration; the Markdown links. The adapter's musl rustdoc with -D warnings, which CI doesn't run, fails with the same five broken links in exec.rs and worker/mapping.rs as at #937's head; this PR doesn't touch them.

Per commit, on Linux x86_64 and on Windows: fmt, Clippy with -D warnings in CI's three configurations and --lib, and tests/host_modules.rs; on Linux, also the aarch64-apple-darwin check, and on Windows, Clippy for the musl target.

This PR needs no VM. #941 runs the runtime adapter's VM suites at the head of the stack, whose guest runs this PR's code: in the CI run below, at ea69821, 58 of 58 passed on KVM, on MSHV, and on WHP.

CI: ci.yml run 40619378275, dispatched on #941's head, ea69821, which covers this PR's code, since this chain's PRs don't target dev and get no PR CI, passed: 36 jobs succeeded, among them nvx-rs and the CLI on Linux and Windows, the guest artifacts, OpenVMM's binaries, unit tests, and vmm-tests, and the NVX microVM tests, with the runtime adapter's VM suites, and the platform jobs on KVM, MSHV, and WHP; the 6 jobs that only pull requests or dev run were skipped.

Tests added (nvx-rs/src/guest/clone/activation/tests.rs):

  • A clone bootstraps, takes one Activate, and opens the fence only once it is ready.
  • No request is accepted before the Activate, and a reset before Ready is final, while one after it starts a later session.
  • Retries join the running activation and get its cached result; a changed record or a new transaction is final; a failed application is final, and no retry applies the record again.
  • An Activate is decoded and validated, with its expected resources, before any side effect.
  • The deadline starts at the acknowledgement, or at the activation for an ungated restore; retries never extend it, and it stays live under trickled input and blocked output.
  • Completions and starts out of order are final; strict failures are returned and never power the VM off; failures name their state and their cause.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The deadline can start after the actual restore acknowledgement, allowing activation beyond the intended timeout.

2 open issues
What changed in this PR

Adds the guest-side activation state machine for repaired clones, the first part of #843. It connects restore repair to configuration acceptance while leaving application and adapter framing to later PRs.

Changes:

  • Adds restricted-session states, record validation, retry handling, deadlines, and terminal failures.
  • Opens the admission fence after successful activation and returns the transaction information for Ready.
  • Adds state-machine tests and documents the public API and adapter responsibilities.
File Description
nvx-rs/​src/​guest/​mod.rs Introduces activation in the guest module documentation.
nvx-rs/​src/​guest/​clone/​activation/​tests.rs Tests transitions, validation, retries, deadlines, and failures.
nvx-rs/​src/​guest/​clone/​activation.rs Implements the activation state machine and public types.
nvx-rs/​src/​guest/​clone.rs Exports activation types and documents the repair-to-activation flow.
nvx-rs/​README.md Describes clone activation and adapter responsibilities.

Review effort: Balanced

Comment thread nvx-rs/src/guest/clone/activation.rs
Comment thread nvx-rs/src/guest/clone/activation.rs
A repaired clone now takes its own configuration from the host through
nvx::guest::clone::Activation, a state machine without I/O and
independent of any framing, to which the adapter passes what its control
session receives, and which returns what the adapter does next:

- In the transport bootstrap, it takes the reset that the restore
  queued for the NVXS session, which the adapter acknowledges.
- The restricted session then takes only flow control and one Activate:
  any other request, or another reset, fails the activation. It decodes
  the Activate's record with nvx::activation, and accepts only a clone's
  record, with a restore section, that expects the CPU count and the
  memory size that the repair reports, before any side effect, then
  hands the record to the adapter, which applies it.
- Once the adapter reports that the application succeeded, it opens the
  caller's fence of workload admission, which nothing before opens, and
  returns the ReadyActivation that Ready reports: that the activation
  applied, with the record's transaction ID. The host's mapping table
  and start then follow in the adapter's order, as after a cold boot,
  and the activation records the start.

A retry with the same transaction ID and a byte-equal record joins the
running activation, or gets its cached result afterwards, also in a
later session, whose reset a ready clone takes. A changed record,
another transaction, another reset before Ready, and a failed
application are final: every later call returns the same
ActivationFailure, which names the state and the cause. The deadline,
which the caller chooses, starts at the restore's acknowledgement, or,
for a restore that the host did not gate, when the caller makes the
activation; no input or retry extends it, and the adapter ticks it
whatever its input and output do. The activation makes no system call
and never powers the VM off: the adapter reports a failure and ends the
VM.

Part of #843

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 8a2f342b-b46d-4a26-a11e-a996e3c1e925

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants