Repository navigation
nvx-rs: track open workload output pipes by their Option alone - #605
Merged
Pedro Henrique Penna (ppenna) merged 1 commit intoOct 19, 2026
Merged
Conversation
The managed agent's exec loop recorded each workload output pipe twice: as an Option<FileDescriptor> that it set to None at end of file, and as a stdout_open or stderr_open flag that it cleared at the same moment. Each check consulted the flag and then unwrapped the Option with an expect that could not fail, and the block that reads a ready pipe, streaming its output to the host or draining it once the session is lost, appeared twice, once per pipe. The Option is now the only record of an open pipe: the loop condition, the poll set, and the straggler check test it directly, and one closure reads either pipe, matching on the Option rather than unwrapping it. The loop loses 35 lines, and no flag can disagree with its pipe. Behavior is unchanged. Each flag was true exactly while its pipe was Some, because both started open and changed together, so every condition evaluates as before. The closure makes the same choice between draining and streaming, reads stdout before stderr, and leaves a pipe that poll did not report ready untouched and open. The loopback test of the real agent covers the loop: it streams stdout and stderr, and ends workloads by exit, signal, timeout, output limit, cancellation, and a session reset, after which the agent drains the pipes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The focused refactor preserves existing behavior, with no blocking issues identified; tests were not rerun.
No open issues 🎉
What changed in this PR
Simplifies the managed agent’s execution loop without changing output handling or termination behavior.
Changes:
- Uses each pipe’s
Optionas the sole open-state indicator. - Shares stdout and stderr read-or-drain logic while preserving output order and limit accounting.
| File | Description |
|---|---|
nvx-rs/src/guest/managed_agent/exec.rs |
Removes redundant pipe-state flags and consolidates output handling. |
Review effort: Balanced
Pedro Henrique Penna (ppenna)
deleted the
nvx-rs-quality/exec-output-pipes
branch
October 19, 2026 15:04
This was referenced Oct 19, 2026
Jay (jay23606)
pushed a commit
to jay23606/nvx
that referenced
this pull request
Oct 19, 2026
A failure of restore-time repair is a fatal time-ABI event: it has the repair source and the restore phase, and it stops the VM with the repair power-off status unless the tools run report-only. commands::repair_failed passed those three to Runtime::fail_fatal, and daemon.rs spelled them out again at six sites, each with G_REPAIR_SUPPRESSION. restore_work also inlined the body of release_suppression. No unit test reached any of them, because Runtime::emit_event opened /dev/kmsg and /dev/console itself, so microsoft#605, microsoft#606, and microsoft#607 left this refactor until a test seam existed. Runtime::repair_failure, beside Runtime::conformance_failure, now passes the repair status, source, and phase. repair_failed and the six daemon sites call it, and restore_work calls release_suppression. Two new PrivilegedOps methods, write_kernel_log and write_console, are the seam. RealOps runs the code with which emit_event wrote an event's kernel log record and console line, and the FakeOps of the unit tests records their bytes, as it already records port writes and power-off requests. Behavior is unchanged. Every repair failure passes the status, code, source, phase, and detail that it passed before. An event writes the same bytes, with the same open flags, to the kernel log, port B, and the console, in that order, and reads the asynchronous-output flag at the same points. A new unit test checks a repair failure's event line at all three destinations and its power-off status, with and without report-only mode, in the glibc and musl test runs. The microVM restore scenarios run restore_work, and so release_suppression, in guests on KVM, MSHV, and WHP. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This was referenced Oct 19, 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.
Summary
The managed agent's exec loop,
runinnvx-rs/src/guest/managed_agent/exec.rs, recorded each workload output pipe twice: as anOption<FileDescriptor>that it set toNoneat end of file, and as astdout_openorstderr_openflag that it cleared at the same moment. Each check consulted the flag and then unwrapped theOptionwith anexpectthat could not fail, and the 15-line block that reads a ready pipe, streaming its output to the host or draining it once the session is lost, appeared once per pipe. This change makes theOptionthe only record of an open pipe and reads both pipes through one closure, so the loop loses 35 lines and no flag can disagree with its pipe.This is the second
nvx-rs-quality/run. The first pull request, #603, is open, with an approving Copilot review and no feedback to follow; it changedprotocol/exec.rs. This run surveyed the rest of the crate, by reading and with a throwaway duplicate-window script, skipped every file that an open pull request todevchanges (#585, #596, #597, #598, and #603), and picked the candidate with the most value per changed line among those that existing tests cover.Changes
All changes are in
runinnvx-rs/src/guest/managed_agent/exec.rs: 10 insertions and 45 deletions. No public item, test, fixture, or other file changes.stdout_openandstderr_openare gone. The loop condition and the straggler-kill condition teststdout_pipe.is_some()andstderr_pipe.is_some(), and the poll set passesstdout_pipe.as_ref()andstderr_pipe.as_ref()directly.One closure,
read_output(pipe, readable, kind), replaces the two read-or-drain blocks. Both pipes now share it:ready[0]andAPP_STDOUT;ready[1]andAPP_STDERR.It matches on the
Optioninstead of callingexpect("stdout reader is open")andexpect("stderr reader is open").Behavior
trueexactly while its pipe wasSome: both started open, and the only writes set a pipe toNoneand its flag tofalsetogether. Every condition and the poll set therefore evaluate as before.output_bytesbudget; any other pipe yieldsStreamResult::Openand stays open. stdout is still read before stderr, so the order of output records and the output-limit accounting are unchanged.expectcalls could not fire, so no reachable panic path changes.nvx-managed-agentkeeps its command line, exit statuses, and protocol bytes.control_session_broker_and_real_agent_loop_back, covers the loop on Linux: it streams stdout and stderr, and ends workloads by exit, signal, real-time signal, timeout, output limit, cancellation, and a session reset, after which the agent drains the pipes. The managed microVM lifecycle scenario andtest-aci-edge-sandboxescover it on every backend.Follow-ups
These turned up in the survey; this change leaves them alone.
guest/time/daemon.rsrepeatsruntime.fail_fatal(PowerOffStatus::Repair, ViolationCode::RepairSuppression, EventSource::Repair, Phase::Restore, ...)six times, andrestore_workinlines the body ofrelease_suppression. ARuntime::repair_failure, besideRuntime::conformance_failure, could serve those calls andcommands::repair_failed. No unit test reaches these paths, becauseRuntime::emit_eventwrites to/dev/kmsgand/dev/console, so the refactor wants a test seam first.The managed-agent tests repeat fixtures:
cgroup::tests::setupanddispatch::tests::direct_systemare identical;mappings::tests::payloadanddispatch::tests::maps_payloadare identical;mappings::tests::setupbuilds the same host-export tree asdispatch::tests::mapping_system.Shared constructors next to
TestSysteminseam.rscould serve all of them.loopback::tests::LoopbackSystemforwards everySystemmethod toTestSystemexceptspawn; a spawn hook onTestSystemwould remove about 50 lines.The unit tests of
console_pending,exit,device_io,mmio_write,reseed,time, andtime_probeeach build a program's argument vector ofCStrings the same way. A helper inguest::sys::testscould serve them once the open pull requests that changereseed.rs,time/mod.rs, andtime_probe/mod.rsland.Validation
BASE:
42c962e47ad4a64dd7511f78969b28745697fbc8, which wasorigin/devwhen the run started. Before this pull request was opened,origin/devhad advanced only by the merge of sandbox: accept only the distro layer and scratch #576 and a performance-baseline update, neither of which changesnvx-rsoraci_edge_sandboxes, and no open pull request todevchangednvx-rs/src/guest/managed_agent/exec.rs.TREE:
cd604cd3cc6bb275b90b9130c76889a4d9eebb9a, the tree of this commit. Every host applied the patch to BASE and checked thatgit write-treeequals TREE.Release on every host:
v0.1.0-dev.f7e8e3e46859. BASE, a[skip ci]data commit, has no release, sodownloadtook the one from the first-parent commit before it. Its OpenVMM,5ab8a84621524f1777b2cb5e65a70dab42c8b453, is the pinned revision.Guest artifacts were rebuilt from TREE on prometheus30, with these steps:
build-guest-binaries --output;test-guest-binaries --binaries, where 34 tests passed;build-initramfswithNVX_GUEST_BINARIES.initramfs.cpio.gz(SHA-25684c49b429a0ff5c2ae6d56b0fd4ddb0da3730840609a0dcf912f9d75b6da8b3f), its packages manifest, and its provenance went to the other two hosts, where the SHA-256 digests matched.Local checks on Windows with Rust 1.95.0:
-D warningsfor the guest binaries' configuration (x86_64-unknown-linux-musl,--no-default-features --features guest, all targets), forx86_64-unknown-linux-gnuwith all targets and features and for its library without default features, and for Windows likewise;cargo test --all-featureson Windows;aarch64-apple-darwin) checks, for bothnvx-rsandaci_edge_sandboxes.test-aci-edge-sandboxestest-microvm, default suitecheck-guest-binaries(277 + 34 musl tests), the loopback agent test among 492 library tests, artifact bundling, and agent bundling; the macOS checks ran locallyLeft to CI:
test-openvmm-unitandtest-openvmm;smp-lapic,managed-exec-config, andsandbox-lifecycle;