Skip to content

tests: settle the runtime adapter's executions through cgroups - #862

Closed
Enrique Saurez (esaurez) wants to merge 3 commits into
devfrom
esaurez/805-pr1-execution-cgroups-20261023
Closed

Enrique Saurez (esaurez) wants to merge 3 commits into
devfrom
esaurez/805-pr1-execution-cgroups-20261023

Conversation

@esaurez

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

Copy link
Copy Markdown
Contributor

Stacked on #878, PR 8 of #822, whose other PRs have merged into dev: #863 → #867 → #869 → #865 → #864 → #872 → #875. This PR retargets to dev once #878 merges.

Part of #805: PR 1 of 3, a cgroup per execution.

What changed

The runtime adapter's worker ran each program in a process group of its own, which it killed before it collected the program's status. Nothing proved that a descendant that left the group was gone, so every outcome was fenced, and the worker could admit a new execution while such a descendant still ran.

  • Each execution gets a cgroup of its own below the workload cgroup, execution-N, prepared with ExecutionCgroup::prepare, which the program joins as it spawns (Command::cgroup(cgroup.placement()?)).
  • It settles through that cgroup with Options::cgroup(&cgroup, Scope::Execution), which leaves the other executions running. An execution whose program ends on its own is now reusable.
  • An execution keeps its place among worker::MAX_EXECUTIONS until its cgroup is empty and the worker removed it. One that does not settle keeps its place and its cgroup until the host stops the worker, which ends and removes every cgroup that is left.
  • RootOperations::cgroups names the cgroups that the bootstrap prepared, in which the worker runs its executions once the root is ready; under runtime adapter: run cold sandboxes end to end on KVM, MSHV and WHP #822's order, it runs none before the host started the sandbox. Where the in-process tests cannot create cgroups, the fake root names none, and the programs keep their process groups, as before.
  • The adapter's README and the worker's module docs describe the cgroups, the places, and the process-group fallback.

Tests

In tests/guest_session.rs, where the runner lets the tests create cgroups (root, in a cgroup v2 hierarchy with the pids controller), each fake root gets a workload cgroup of its own, and every execution test runs with cgroups, its outcomes reusable. Elsewhere, the tests run with process groups, as before. New tests, with cgroups:

  • a descendant in a session of its own, which holds the program's output once the program ended, and which the settlement ends: the output ends at once, losing nothing, the worker removed the cgroup before it sent the outcome, and it runs its limit of executions again;
  • an execution that does not settle, because a zombie whose parent is outside its cgroup occupies the cgroup: it keeps its place, even once the cgroup emptied, until the host stops the worker, which removes the cgroup.

Validation

This branch is rebased onto #878's current head (47f8bcd), the maintainer's re-commit of #878 on dev after #875, #907, #908 and #911 merged; the OpenVMM gitlink stays 2b681564. The rebase had no conflicts, and git range-diff shows the three commits' patches unchanged (3855029 → 878906a, a012812 → 229c58d, ec3af7d → a8cc6e2).

  • Each of this PR's commits passes Clippy with warnings denied in the protocol-only, host-only and guest-only configurations.
  • Windows, at a8cc6e2: rustfmt, Clippy and the tests of the protocol-only and host-only configurations, all passing.
  • At ba8a5f6, the head of runtime adapter: settle executions through cgroups and run them in a VM #805's PR 3 (tests: run the runtime adapter's executions in VMs through its host program #870), which holds this PR and PR 2 (tests: run the runtime adapter's executions in the root, under their identity #868) on the same base:
    • Linux, on a cgroup v2 host: the adapter's steps of check-nvx-rs in its three configurations, with rustfmt, Clippy with warnings denied, the build, the tests, and cargo +1.89 check (the MSRV): protocol-only, host-only, and the guest-only x86_64-unknown-linux-musl configuration; and scripts/check_markdown_links.py;
    • the guest-only tests as root, 50 library tests and 48 guest_session tests; then 10 runs of guest_session as root, with cgroups, and 10 unprivileged, with process groups, and 3 runs of the library's tests, all passing, with no cgroup left behind;
    • Windows: 10 runs of host_session, all passing;
    • the adapter's VM suites with a release build of that head's guest agent and the v0.1.0-dev.c2ace076c60a artifacts (OpenVMM 2b681564): on WHP, locally, all 35 layered scenarios, the 3 restart tests and the 3 step tests pass; on MSHV, on a Linux test host, as root, with OpenVMM's --cpu-profile host, under a short temporary directory, which keeps the VMs' control sockets' paths within SUN_LEN, so do all of them. KVM is left to CI. This PR adds no VM scenario; PR 3 of runtime adapter: settle executions through cgroups and run them in a VM #805 adds those that run executions.

Copilot AI balanced review requested due to automatic review settings October 24, 2026 07:23

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

Fixture cleanup can mask a worker shutdown regression, so the cgroup-removal assertion needs to run before the fixture is dropped.

1 open issue
What changed in this PR

This PR implements the first part of #805 by giving each runtime-adapter execution its own cgroup, allowing settlement to account for descendants that leave the program’s process group.

Changes:

  • Creates per-execution cgroups and retains execution slots until cleanup, keeping unsettled executions fenced until shutdown.
  • Connects root bootstrap cgroups to the worker while preserving the process-group fallback.
  • Adds cgroup lifecycle tests and documents settlement and admission behavior.
File Description
tests/​runtime-adapter/​tests/​guest_session.rs Adds cgroup fixtures and descendant-settlement and shutdown tests.
tests/​runtime-adapter/​src/​worker/​executions.rs Implements cgroup placement, settlement, slot retention, and cleanup.
tests/​runtime-adapter/​src/​worker.rs Supplies bootstrap cgroups to executions and documents their lifecycle.
tests/​runtime-adapter/​README.md Describes cgroup behavior, fallback behavior, and test coverage.

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 24, 2026 07:58

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.

🔵 Needs a closer look

Privileged cgroup settlement and teardown warrant human confirmation, with VM validation still pending in CI.

1 open issue

Review effort: Balanced

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.

🔵 Needs a closer look

No blocking defect was identified, but asynchronous cgroup settlement and shutdown sequencing warrant final human review.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@esaurez
Enrique Saurez (esaurez) changed the base branch from dev to esaurez/822-pr08-mapping-tables-vms-20261023 October 24, 2026 20:04
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/805-pr1-execution-cgroups-20261023 branch from a40048e to 20d5cdc Compare October 24, 2026 20:04
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 24, 2026 20:07

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.

🔵 Needs a closer look

Privileged cgroup placement, deferred process reaping, and shutdown cleanup warrant final human review despite no identified blocking defects.

No open issues 🎉

Review effort: Balanced

if binding.execution().status().is_none() {
self.reaping.push(binding.id());
}
drop(binding);

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.

Why does binding need to be drop manually? Are we required to release its resources before the next stament? Could we rely on automatic drop semantics instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, the explicit drop isn't required. Nothing after it depends on the binding being gone, so the automatic drop at the end of scope behaves the same. The explicit drops only marked where a program that hasn't ended gets killed.

Your 21fd36f already relies on automatic drop. It removes drop(binding); from clear() (line 282 of the version you commented on) and let key = binding.runner().key(); drop(binding); from forget() (lines 386-387), names binding.runner().key() in forget()'s diagnostic instead, and updates forget()'s doc to say that the binding drops last. I checked that the new order is equivalent:

  • clear(): each binding still drops at the end of its own loop iteration. So every program that hasn't ended, with its process group or its cgroup's processes, is still killed before the cgroup.kill loop over fenced and the reaping wait. self.fenced.extend(cgroup) only stores the handle.
  • forget(): the binding now drops after the cgroup's removal. That's safe because settled comes from a reusable outcome, which Binding::settle reports only after it finds the cgroup unoccupied and calls Execution::release_cgroup(). By then no process or zombie is left in the cgroup, which ExecutionCgroup::remove checks anyway, and the binding's drop no longer touches it. An unsettled execution's cgroup only goes to fenced, and the drop still kills its processes before the stop's clear() kills and removes what's left.

So your head needs nothing more from me. With 5a071a5 checked out, rustfmt and Clippy in the guest-only configuration pass. I'll leave the thread open for you to resolve.

@ppenna
Pedro Henrique Penna (ppenna) changed the base branch from esaurez/822-pr08-mapping-tables-vms-20261023 to dev October 25, 2026 13:20
@ppenna
Pedro Henrique Penna (ppenna) changed the base branch from dev to esaurez/822-pr08-mapping-tables-vms-20261023 October 25, 2026 13:20
@ppenna Pedro Henrique Penna (ppenna) removed their assignment Oct 25, 2026
@ppenna
Pedro Henrique Penna (ppenna) force-pushed the esaurez/805-pr1-execution-cgroups-20261023 branch from 20d5cdc to 5a071a5 Compare October 25, 2026 13:37
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/822-pr08-mapping-tables-vms-20261023 branch 2 times, most recently from 9f0c445 to 090de0a Compare October 26, 2026 20:13
@esaurez
Enrique Saurez (esaurez) force-pushed the esaurez/805-pr1-execution-cgroups-20261023 branch from 5a071a5 to ec3af7d Compare October 26, 2026 20:38
@esaurez
Enrique Saurez (esaurez) requested a balanced review from Copilot October 26, 2026 20:38

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.

🔵 Needs a closer look

Cross-process placement, status collection, and shutdown cleanup warrant final human review despite no concrete blocking issue being identified.

No open issues 🎉

Review effort: Balanced

Enrique Saurez and others added 3 commits October 26, 2026 15:47
The worker ran each program in a process group of its own, which it
killed before it collected the program's status. Nothing proved that a
descendant that left the group was gone, so every outcome was fenced,
and the worker could admit a new execution while such a descendant
still ran.

Each execution now gets a cgroup of its own below the workload cgroup,
which the worker prepares with ExecutionCgroup::prepare, and which the
program joins as it spawns. The execution settles through that cgroup
alone, so the other executions keep running, and an execution whose
program ends on its own is reusable. An execution keeps its place among
the worker's limit until its cgroup is empty and removed. One that does
not settle keeps its place, and its cgroup, until the host stops the
worker, which ends and removes every cgroup that is left.

The root's operations name the cgroups that the bootstrap prepared. A
fake root names none where the in-process tests cannot create cgroups,
and the programs keep their process groups there, as before. Where the
tests can, each fake root has a workload cgroup of its own, and new
tests check a descendant in a session of its own, which the settlement
ends, and an execution that does not settle, which keeps its place
until the stop.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a7ad408a-da3b-45c4-aa02-5d25300b1c90
A program that the worker spawned, but whose execution it could not
have, may still be joining its cgroup, as its launch has yet to report,
and removing the cgroup could then hide the program once it ended, which
ExecutionCgroup::remove forbids. Such a start's cgroup now keeps its
place, as that of an execution that did not settle, and a stop removes
the executions' cgroups only once the worker collected the status of
every program that it forgot.

The holder in the test of an execution that does not settle now runs in
a process group of its own, which its drop kills, so that its child no
longer outlives a test that fails before the child joins the cgroup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a7ad408a-da3b-45c4-aa02-5d25300b1c90
Once the in-process tests create cgroups, each fake root holds a
workload cgroup of the test's own, which ends what is left in it and
removes the cgroups of executions when it drops. Two tests discarded the
fake root that a stop returns, so that it dropped before their checks:
that the stop removed the cgroup of an execution that did not settle,
and that it left no process of a program's group running. Those checks
then held whatever the worker did. The tests now keep the fake root
until their checks are done.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a7ad408a-da3b-45c4-aa02-5d25300b1c90

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.

🔵 Needs a closer look

Privileged cgroup placement, zombie handling, and timing-sensitive shutdown cleanup warrant final human review despite no confirmed defects.

No open issues 🎉

Review effort: Balanced

Base automatically changed from esaurez/822-pr08-mapping-tables-vms-20261023 to dev October 26, 2026 23:17
@esaurez

Copy link
Copy Markdown
Contributor Author

Closing: these changes are in dev through #868 (merge commit ef7c177), which carried this PR's commits in the linear stack. git range-diff shows them unchanged: 878906a, 229c58d and a8cc6e2 are e83119b, ae5bee0 and 9539005 there.

@ppenna
Pedro Henrique Penna (ppenna) deleted the esaurez/805-pr1-execution-cgroups-20261023 branch October 27, 2026 15:34
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.

3 participants