Repository navigation
tests: a guest runs only where the host has a CPU for each of its vCPUs - #1166
Conversation
The harness ran guests side by side with no regard for the CPUs they need: a 12-wide run put the four 8-vCPU virtsmpcase guests beside each other, and beside every other worktree's guests, on a 14-core host. A vCPU that shares a host CPU is descheduled, the kernel's FIFO ticket lock then hands over at the host's scheduling pace, and test_rs_counters_read goes quiet past GUEST_QUIET. Ticket locks stay; the oversubscription is the harness's to stop. `buildlock::guest_cpus` keeps one flock per host CPU in the host store, so every worktree's harness shares the budget. A guest takes one per vCPU for its life, and a guest wider than the host takes all of them. A take is all or nothing, so no guest holds CPUs while it waits for more. The head waiter holds a queue lock, so narrower guests cannot overtake a wide one for ever. QemuInstance takes the CPUs after its image is built and before QEMU starts, and gives them back after Drop has reaped QEMU. GUEST_QUIET stays unscaled. Its comment, and ceiling_verdict's, relied on a periodic kernel line the idle report's removal took away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aves out is recorded `a-counters-read-under-host-load-can-go-silent-for-15-s` asked for the harness to admit a guest only where the host has a CPU for each of its vCPUs, and for the four virtsmpcase rows to pass under the parallel load that silenced them. Two harness invocations of the four rows ran at once, beside other worktrees' work at host load 19 to 53, over four interleaved rounds. Main (with #1160) went silent in 5 of 32 rows, and its reads took up to 15.6 s. With the budget, no row of 32 went silent and 28 reads of 32 took under 2.4 s. One budgeted read still took 14.22 s with only its own guest admitted: the budget counts guests, and builds and other non-guest work are not in it. `the-guest-cpu-budget-counts-guests-and-not-builds` records that. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #1166 at 28a9812 (r0)Two commits on ArchitectureThe harness that makes the oversubscription is the right owner. Putting the budget in the host store is the right scope, since the contention spans worktrees. Each CPU is one What I checked
RequiredR1. R2.
Two fixes are needed:
R3.
R4. Fix: retry the probe until one R5. Missing evidence: suite wall time.
Cheapest sufficient check: the full guest suite ( Suggestions (optional)
Evidence checked
SEND BACK |
…guest that can still be starved is said - The budget is `TOYOS_HOST_CORES` only where it is below the host's real count (`admitted_cpus`), so the override narrows admission and never re-admits the oversubscription. - `GUEST_QUIET`'s doc names the guests the budget does not cover: one wider than the host, and one beside builds. The record is renamed to say so and adds the four-core nightly runners, where the 8-vCPU rows are wider than the host. - A thread that boots a guest while it holds another's CPUs panics at once, rather than waiting on itself until the six-hour ceiling. - Every panic before an instance exists reaps QEMU first, so the CPUs and the NVMe claim are released after QEMU is gone on those paths too. - The free-CPU probe in the host test tries until one take lands, since each of the waiter's attempts and a sibling's fork hold the CPU for a moment. - The moved helpers no longer split `a_queued_exclusive_phase_goes_first`'s doc; the queue's note says "another guest", and one long line is wrapped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clippy: the error was a String already, so the clone went. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Intermittent records that name a deleted test are gone through. Eleven of them were seen only on a loaded host and have no defect a remaining test can reach. They are closed, each for this reason: - a-netcase-boot-under-host-load-said-nothing-past-the-firmwares-screen-clears: netstack_socket_churn is gone. A boot that fell silent under load, with no mechanism found. - a-shared-boot-stopped-answering-and-no-capture-says-why: port_poll_churn and the 12-wide TCG shared tier are gone, and the stop was never seen again. - consent-prompts-click-on-an-open-launchers-row-made-no-launch: consent_prompt is gone. Seen once, at host load 34 to 47. - consent-prompts-skip-lost-its-prompt-and-the-hostile-window-at-host-load-100: consent_prompt is gone. Seen once, at host load 100. - log-reserve-window-negative-times-out-beside-other-guests: the test is gone. A boot timeout beside other guests. - metal-job-reboot-drained-no-kernel-output-beside-other-guests: the test is gone. Red on main and branch alike, every run beside other worktrees' load. - smp-ap-hole-and-log-reserve-window-red-under-a-loaded-host: both tests are gone. One WouldBlock while two other suites shared the host. - stdio-buffering-saw-one-long-line-arrive-as-two: 90_stdio_buffering is gone. One split line beside other guests. - syscall-window-nmi-shortfalls-on-a-contended-host: syscall_window_nmi is gone. A sampling shortfall on a contended host. - the-console-loses-typed-keystrokes-under-host-load: screen_console_scroll is gone. Its mechanism, keys typed through QMP lost under host load, is tracked in a-command-typed-through-qmp-arrives-truncated-under-host-load. - a-syscall-panic-reset-the-guest-announced-and-qemu-never-saw: syscall_panic_halts is gone with the reset_now path it drove. Red once, at a 2.96x liveness width on a loaded host, and nothing measured reproduces it. Metal has no QEMU SHUTDOWN event, so no planned test could retire it. Records that cited these files now carry what they took from them: parallel-tests-red-under-other-suites, guest-tests-a-loaded-host-can-turn-red, two-amdvi-boots-in-a-kvm-suite-wrote-not-one-byte, two-processes-started-at-an-entry-point-nothing-had-mapped (with the commits that deleted log_reserve_window), and one doc line in tests/toyos.rs. Records of real defects that no remaining test reaches stay open, and say which commit deleted their test: so-cache-refusals-saw-the-kernel-refuse- nothing-once (4505f87) and a-double-panic-at-boots-edge-says-nothing-but- its-name (ad6dc07). The double panic's owner is whoever holds the K4 stage of the-kernel-still-creates-threads, open as #1165, and its reproduction recipe goes with its test. a-holders-queued-line-can-reach-the-console-after-the-last-word cites that stage by path, quotes what it states and names that track's holder as owner. libc-childrens-tty-interrupt-can-stall-at-the-nested-shells-line gains its 2026-11-04 sighting at the nightly's shape, with #1166's evidence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Problem
The harness ran guests side by side with no regard for how many host CPUs they need. The four
virtsmpcaserows boot 8 vCPUs each, and a 12-wide run put them beside each other and beside everyother worktree's guests on a 14-core host. A guest whose vCPUs share host CPUs has them descheduled.
The kernel's FIFO ticket lock then hands over at the host's scheduling pace (#1160's captures), and
test_rs_counters_readgoes quiet pastGUEST_QUIET(
issues/a-counters-read-under-host-load-can-go-silent-for-15-s.md). Ticket locks are kept, asdecided on 2026-11-04, so the fix belongs where the oversubscription is made.
Change
toyos_build::buildlock::guest_cpus: oneflockper host CPU in the host store(
~/.cache/toyos/locks/guest-cpus/cpu-N), so every worktree's harness on the host shares thebudget. A guest holds one per vCPU for its life. A guest wider than the host takes all of them.
no guest holds some CPUs while it waits for others, and two waiters cannot deadlock.
queuewhile it waits, so narrower guests do not overtake awide one forever. Nothing waits on
queuewhile holding a CPU.flock), as for the build locks.QemuInstance::boot_with_optionstakes the CPUs after the image is built and before QEMU starts,so the wait is never charged to a guest's clocks, and holds them until
Drophas reaped QEMU.GUEST_QUIETstays unscaled. Its doc comment said the kernel speaks every 10 s; that report wasremoved on 2026-10-04, and the comment now says what holds instead.
ceiling_verdict's doc losesthe same premise.
What this does not cover: QEMU guests started outside the harness (
cargo run), and host load thatis not guests, such as builds.
Evidence
Logs, tallies and scripts are in the evidence branch: r1 in
pr-1166/r1, r0 inpr-1166/r0.Parallel load, the shape that silenced the rows. Each round ran two harness invocations of the
four 8-vCPU rows at once (64 vCPUs asked of 14 cores), beside other worktrees' work at host load
19 to 53. The arms were interleaved over four rounds, 32 rows each. The base is main at
65c77e6dd, which already carries #1160's one-copy read; it is this change reverted.counters_read(guest clock)Each budgeted guest waited its turn: the logs show
waiting for the host CPUs (8 of 14, …)andthen
acquired; two 8-vCPU guests cannot hold 16 of 14. The 14.22 s read ran with only its own guestadmitted. It is what non-guest load still does, and it is the new record's evidence.
The nightly's shape (r1).
guest.ymlrunscargo run -- --ci guest, which runs the suite with--jobs 1(build/ci.rs:133). A four-core runner never has two guests from the suite at once, so thebudget changes nothing there but a lock take per boot. The suite's time against the 60-minute guard
stays what it was. Measured in that shape on the dev host,
--jobs 1withTOYOS_HOST_CORES=4, overthe four 8-vCPU rows and
libc_children, one run per arm:The head run's
libc_childrenfailed intty_interrupt, at the nested shell's line, after 207 s.That is the recorded
libc-childrens-tty-interrupt-can-stall-at-the-nested-shells-line, which did notwait for CPUs here either.
What the budget does cost is local wide runs. On the 14-core dev host at 12 wide, the running guests' vCPUs now sum to
at most 14: one 8-vCPU guest and three 2-vCPU ones, or seven 2-vCPU ones. That cost is not measured.
Ambient load, one invocation. Six rounds before #1160 landed, at host load 18 to 36, 24 rows
per arm: main 7 silent, this change 1 silent.
Checks
cargo test -p toyos-build --lib -- buildlock::tests::a_guest buildlock::tests::a_narrow: threenew tests, green. Each waits on another thread's report with a bound, never on a sleep that has to
pass.
controls/):a waiting guest held a CPU.Both controls ran before r1's probe retry; M1 leaks the CPU for good, so the retry still reds it.
cargo run -- --ci hostat69f7f7292, the head: exit 0, 109 steps green (r1/host-ci.log.gz).ca16c59f2. Review r1 changes no admission for a guest no wider than its host on a host whosereal core count is at least
TOYOS_HOST_CORES, so they stand. The nightly-shape run is at the r1 code.Metal: none. The change is in the QEMU harness.
🤖 Generated with Claude Code