Repository navigation
A guest test waits for a kernel record and for a program's line each, where it read one off a capture the other ended - #786
Conversation
…it reads it The test waited for the kernel's record of the sleep type and then asserted acpiserver's armed line on the capture that wait ended. The server says the armed line first, but a program's line reaches the console when logkeeper has read the program's ring and queued it, and the kernel's record does not go that way: a capture that ends at the record need not hold the line. Seen red once, beside machine_shutdown and acpi_power_button in one filtered run on a host at load 51 (1 min), with the kernel's record stamped 15.521 s and no program's line stamped after 15.175 s; green alone at load 45. It now waits for the armed line itself, as acpi_power_button does, and then for the record, each under await_marker's own guard, so a server that never arms is a stall named for what it waited on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
The negative control's patches ( Stage A, the armed line said 3 s after --- a/userland/acpiserver/src/main.rs
+++ b/userland/acpiserver/src/main.rs
@@ -102,6 +102,8 @@
};
server.arm();
server.power_off = aml::load(&Claim(&server.dev), server.info.rsdp).handed;
+ std::thread::sleep(Duration::from_secs(3));
+ println!("acpiserver: armed: power button served, embedded controller none");
server.serve();
}
@@ -172,14 +174,6 @@
self.run_queued();
}
self.dev.ack().expect("acpiserver: the claim's acknowledgement");
- println!(
- "acpiserver: armed: power button {}, embedded controller {}",
- if self.served.power_button { "served" } else { "not the fixed one, so not served" },
- match self.served.ec_gpe {
- Some(gpe) => format!("on GPE {gpe:#x} at {:#x}/{:#x}", self.info.ec_command.port, self.info.ec_data.port),
- None => "none".into(),
- },
- );
}
fn serve(&mut self) -> ! {Stage B, the armed line never said ( --- a/userland/acpiserver/src/main.rs
+++ b/userland/acpiserver/src/main.rs
@@ -172,14 +172,6 @@
self.run_queued();
}
self.dev.ack().expect("acpiserver: the claim's acknowledgement");
- println!(
- "acpiserver: armed: power button {}, embedded controller {}",
- if self.served.power_button { "served" } else { "not the fixed one, so not served" },
- match self.served.ec_gpe {
- Some(gpe) => format!("on GPE {gpe:#x} at {:#x}/{:#x}", self.info.ec_command.port, self.info.ec_data.port),
- None => "none".into(),
- },
- );
}
fn serve(&mut self) -> ! {The fix reverted is this branch's own diff of diff --git a/tests/common/power.rs b/tests/common/power.rs
index 7ba2ad806..43de9bf0d 100644
--- a/tests/common/power.rs
+++ b/tests/common/power.rs
@@ -92,10 +92,14 @@ pub fn machine_shutdown_short_stop(test_config: &Path) -> Result<(), String> {
let boot = serial::Serial::boot(&qemu);
boot.must_be_clean()?;
let mut console = boot.text().to_string();
+ // A wait each: the server's line reaches the console through `logkeeper`
+ // and the kernel's record does not, so a capture that ends at either need
+ // not hold the other.
+ qemu::await_marker(&mut qemu, &mut console, ACPI_ARMED, "the ACPI server arming")?;
qemu::await_marker(&mut qemu, &mut console, Q35_S5_SUPPLIED, "the ACPI server to hand the kernel \\_S5")?;
- let armed = serial::Serial::named("boot", console.clone());
- armed.must_say(ACPI_ARMED)?;
- armed.must_say("acpi: the firmware handed this machine over in ACPI mode, so nothing is written")?;
+ // The kernel's own record, committed before the one just waited for.
+ serial::Serial::named("boot", console.clone())
+ .must_say("acpi: the firmware handed this machine over in ACPI mode, so nothing is written")?;
let mut stop = qemu::QmpShutdown::open(qemu.qmp_socket(), qemu.budget(qemu::GUEST_QUIET));
writeln!(qemu.stdin_mut(), "run test_rs_stop_short").expect("write to QEMU stdin"); |
|
Review of Net lines: +7 -3, all in tests; production 0. BLOCKER
Judged and found sound
NOTE
What the landing head must showThe fix changes the head, so every measurement is retaken there: SEND BACK |
…e was read off a capture the other ended A kernel record goes on the console as klogd drains the kernel's ring, and a program's line once logkeeper has queued it; klogd takes at most CHUNK of each per hold of the wire, so neither kind is held back for the other and a capture that ends at one need not hold the other. The short stop was the one site seen red. The review of that fix named two more of the same shape, and a sweep of every wait in tests/common and tests/toyos.rs found three: - acpi_power_button: the kernel's ACPI-mode record, read off the capture the server's armed line ended. - virt_reboot_refused_without_psci: the kernel's refusal, read off the capture test-runner's end of the job ended. - virt_smp, virt_el1_smp: each CPU's "joining scheduler", said after the machine is released, read off the capture the first job's end ended. - virt_job (seven tests): the kernel's record of logkeeper's spawn, which bootlog::one_clock owes, read off the capture the job's end ended. - iommu_virtio_platform: the kernel's records of the claim (the BAR moved and MSI-X armed, or the two refusals), read off the capture the daemons' lines ended. Each now waits on its own marker under await_guest's existing guard. A record the kernel makes before klogd starts is not waited for: kernel_main drains it inline, before any program exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
Answer to the round-1 review, at BLOCKER 1:
|
| step | what | exit |
|---|---|---|
r3-01 |
the 14 guest tests the change reaches | 1: 13 passed, virt_smp stalled |
r3-01b |
virt_smp alone |
0 |
r3-02 |
stage C, virt_reboot_refused_without_psci |
1, by name |
r3-03 |
stage D, acpi_power_button |
1, by name |
r3-04 |
tree after the controls | clean |
r3-05 |
cargo test --test toyos-checks |
0 |
r3-06 |
cargo run -- --clippy |
0 |
r3-07 |
cargo run -- --ci host |
0 |
r3-09 |
the 14 again | 0: 14 passed |
The red in r3-01 is virt_smp: STALLED: waiting for the job test_rs_counters_read to end — it went quiet: the guest went silent after the kernel's spawn record of that job, past every wait this change adds, at host load 30 rising to 50. It is a hang ceiling in a wait this branch does not touch; the body records it with its load, and it is not yet in issues/, which is outside this branch's fence.
The control patches
Neither is committed. Each was applied with git apply --check, run once, and reversed.
Stage C, the reboot's refusal record withheld:
--- a/kernel/src/syscall/machine.rs
+++ b/kernel/src/syscall/machine.rs
@@ -160,7 +160,6 @@
return e.refuse();
}
if !power::can_reboot() {
- log!("reboot: this machine has no reset this kernel performs — refused");
return SyscallError::NotSupported.to_u64();
}
if let Err(e) = quiesce("Rebooting.") {Stage D, the ACPI-mode record withheld:
--- a/kernel/src/arch/x86_64/acpi_mode.rs
+++ b/kernel/src/arch/x86_64/acpi_mode.rs
@@ -352,7 +352,6 @@
if ENABLED.load(Ordering::Relaxed) {
log!("acpi: this machine is still in ACPI mode after an ACPI_DISABLE its firmware did not act on, so nothing is written");
} else {
- log!("acpi: the firmware handed this machine over in ACPI mode, so nothing is written");
}
return Ok(());
}|
Review of Net lines against Round 1's findings
The three sweep sitesEach is the same defect, fixed the same way, and none adds a wait that can stall a healthy boot.
No flat wait: every one is The red in
|
`virt_smp` stalled once in this branch's round-3 guest run, in `judge_virt_job`'s wait for `test_rs_counters_read` to end, a wait this branch does not touch. The defect is `main`'s and already has its file; the file gains this occurrence: the line, the host's load, the wait that ended it, what differs from the first silent boot, what the run shows and what it does not. Each number was read off that run's log, which is not kept in the tree. No register capture exists for this one either, so the exit is no closer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
|
CI at |
No conflict and no file of this branch's: #786 changes three harness files and one issue. It is merged because the whole suite at the merge of 5f26577 (94fd25e) was red on machine_shutdown_short_stop, which read the ACPI server's armed line off a capture the kernel's record had ended, and #786 is main's fix of that wait. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…e call (#780), into stage 1 of the one boot No hunk conflicted. tests/toyos.rs took both sides: this branch's are the skip comment on acpi_hold, TESTCASES_HELD and its two rows, and the count line read from acpiserver-api; main's are the waits #786 added, #780's firmware calls in acpi_mediated_access and counters_on_metal. This branch changed no line of tests/common/power.rs, so main's stands whole. issues/machine-shutdown-short-stop-asserts-a-program-line-it-never-waited-for.md goes: this branch filed it, and #786 met its exit, machine_shutdown_short_stop waiting for ACPI_ARMED before it reads it. Nothing in the tree cited it by path or by slug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
…ers (#777), the drivers' room and wake (#782), the SMI_CMD call (#780) and the guest waits (#786), into the resolver rules One conflict, in issues/toyos-has-its-own-network-stack.md, resolved by hand with every line of both sides accounted for: - The stage 5 paragraph: main's "In the tree", which now names streams, listeners and the one bound, and this branch's "Still to build on it", less the streams and listeners that landed: datagram senders that wait on the hop, then the move. - "What the node does not yet meet": this branch's two lines stand in place of the two lines they rewrote, which main had left as they were; the line on an answer to a 169.254/16 asker, which #779 deleted with the defect, is gone, as is the line on a silent next hop, which #779 deleted with its test (no conflict). - "What stage 3 departs from its specifications": both sides added a line at the list's end; both stand, this branch's on NUD-04 and #779's on the scenarios the readers' specification lacks. Every other file merged without a conflict. toyos-dns/src/lib.rs and userland/netstack/node/src/resolve.rs are byte-identical to b2d9037. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A
A guest test that read a kernel record off a capture a program's line ended, or the reverse, now waits for each. Six sites had the shape:
machine_shutdown_short_stop, the one seen red, and five found by reading.The defect, as recorded
Recorded in
issues/machine-shutdown-short-stop-asserts-a-program-line-it-never-waited-for.mdonwt/toyos-oneboot-b1(#785, not landed), whichmainnever held. What it recorded:machine_shutdownandacpi_power_buttonin one filtered run, on a host at load 51 (1 min) running other worktrees' builds. Alone, two minutes later at load 45 on the same tree, it passed in 12 s. The branch it was seen on changed no line oftests/common/power.rs.FAIL machine_shutdown_short_stop: "acpiserver: armed: power button served, embedded controller none" never reached the boot:over a capture that held the kernel's record stamped 15.521 s and no program's line stamped after 15.175 s.power: S5 is PM1a 0x604 with SLP_TYPa=0,and assertedacpiserver's armed line on the capture that wait ended. The two reach the console by different roads: a kernel record goes on the wire asklogddrains the kernel's ring, and a program's line only oncelogkeeperhas read that program's ring on its cadence and queued the line.klogdtakes at mostCHUNK= 8 records and then 8 queued lines per hold of the wire (kernel/src/log/console.rs), so neither kind waits for the other, in either direction, and a capture that ends at one need not hold the other.#785 is asked to delete its issue file when it merges
main: this change meets its exit condition.The fix
Each site waits on its own marker with
qemu::await_marker, which isawait_guest: ended by the marker, byGUEST_QUIETof silence, or bybudget(GUEST_WEDGED). No flat wait, and no new helper or test. A marker that never comes isSTALLED: waiting for <what>, by name.machine_shutdown_short_stop(tests/common/power.rs)power: S5 isrecordacpiserver's armed line, then the recordacpi_power_button(tests/common/power.rs)acpiserver's armed linevirt_reboot_refused_without_psci(tests/toyos.rs)test-runner's===TEST_END rebootreboot: … refusedrecordvirt_smp,virt_el1_smp(tests/toyos.rs)test-runner's===TEST_END unmap_touchCPU n: joining scheduler, said after the machine is releasedvirt_job, seven tests (tests/toyos.rs)test-runner's===TEST_END <job>logkeeper's spawn, whichbootlog::one_clockowesiommu_virtio_platform(tests/common/iommu.rs)NOT HANDED OVERwhere there is noneThe first row is round 1's; the second and third are the review's; the last three are this round's sweep. Vs
main: +52 -21 in three test files, and the one issue file below; nothing ships.In
machine_shutdown_short_stopthe ACPI-mode record is still read off the capture: it and the record just waited for are both the kernel's, the earlier committed before the later is reserved, on one CPU.The sweep
The criterion: a kernel record asserted on a capture that a program's line ended, or the reverse, with no code ordering the two. Two things do order them, and a site that rests on one is not the shape:
klogd. Every recordkernel_mainmakes is drained inline by its producer (kernel/src/log/mod.rs,Drain::Inline):klogdis started askernel_main's last act before the idle loop, and no task runs before that. Such a record is on the wire before any program exists. An AP's control-register line is among them: it is logged before the AP's echo, and the boot CPU logs after the echo.toyos/src/power.rs): it haslogkeeperflush, which feeds the console, before it asks the kernel. The kernel stops every thread and puts every queued line on the wire (drain_for_the_stop) before it logs the last word, andsettle's records come after that.Every wait in
tests/common/andtests/toyos.rswas read (await_guest,await_marker,drain_until,await_exit,run_test, and each reader ofboot_log()). Beside the six fixed:tests/common/qemu.rsboot_with_options, on the boot log that ends at===READY===:boot: root=iskernel_main's; the build line is the supervisor's, a program's beside a program's.run_test_paced: a result'sstdoutholds programs' lines only (push_user_halffiles a kernel line out), and its window already moves the kernel's spawn record in by name.tests/common/faults.rsnested_nmi_is_loud: the fatal path's own raw lines up to its own arm line; no program's.tests/common/iommu.rsdeclining_is_not_free, on the boot log:Boot: complete, the refused feature sets and the console'saccess_platform=yare allkernel_main's device bring-up.iommu_virtio_platform, the rest:Boot: complete, the PCI walk and the kernel's ownVirtIO: PCIlines arekernel_main's; netstack's feature line was already waited for.tests/common/power.rsmachine_shutdown: waits for the kernel's record and asserts nothing else on that capture.ended, and what follows it inacpi_power_button,machine_shutdown_short_stopandended_through_psci's callers (virt_smp,virt_reboot,virt_off_names_the_cpus_left_on): read after QEMU has exited, past the stop.metal.rs,audio.rs,claims.rs,isa.rs,usb.rs,irqcensus.rs: a readback, which is a finished boot.tests/toyos.rsjudge_virt_job: the job's line andtest-runner's end of it, both programs'.acpi_mediated_access: waits for the kernel's lock record of the stop, whichsettlemakes after the last word; the probe's lines went on the wire before that word.acpi_supply_outlives_holder,virt_mask_windows: wait for the last word.virt_failed_ap_leaves_no_hole: theSMP:lines arekernel_main's. (virt_smp'sPSCI:andSMP:lines and its control-register lines likewise; only the joins were not.)console_image_boots: waits for the console's line and asserts only absences.virt_selftest,virt_user_mode,the_others_halt_first,virt_early_panic,virt_el2_drop,virt_early_fault: kernel records only.screen_loader_clears: the loader's lines only.screen_panic_muted,screen_fatal_halt_composited: no console.screen_fatal_behind_a_painter: read after QEMU has exited.netstack_socket_churn,bar_map_again: a program's line waited for, thenrun_test.run_debug_mode: asserts nothing.Nothing was found outside the fence, so no issue is filed for the shape.
Gates, at
0c0a348e1(this branch,mainat6776714dbmerged)The head is
f9006e888:git diff 0c0a348e1 f9006e888 --statis that one issue file, +37 -2, so nothing below was run again. At it,cargo test --lib sourcegateexits 0 (10 passed).Logs are named by round and step; each opens with its command and ends with its exit code. The host is a 14-core machine in power-saving mode running other worktrees' builds; an x86-64 guest and the EL2
virtprofile run under TCG on it.r3-01cargo test --test toyos-build -- --nocaptureover the 14 tests the change reaches (below)virt_smpstalledr3-01bcargo test --test toyos-build -- --nocapture virt_smp, aloner3-05cargo test --test toyos-checksr3-06cargo run -- --clippyr3-07cargo run -- --ci hostHost: 78 step(s), all green)r3-08git status --porcelain --ignore-submodules=noneafter everythingr3-0914 passed, 14 totalThe 14:
machine_shutdown,machine_shutdown_short_stop,acpi_power_button,iommu_virtio_platform, and on the profilestests/toyos.rsregisters them under,virt_reboot_refused_without_psci,virt_smp,virt_el1_smp,virt_timer_preempts,virt_fp_isolation,virt_first_entry,virt_unmap_touch,virt_debug_refused,virt_readonly_copyout,virt_ring0_timer_in_syscall.The red in
r3-01, recorded with its loadFAIL virt_smp: STALLED: waiting for the job test_rs_counters_read to end — it went quiet, with nothing said while it was waited on. The guest, 8 CPUs under TCG, had passed every wait this change adds: its last lines are===TEST_END unmap_touch exit=0===,===TEST_START test_rs_counters_read===and the kernel's spawn record of that job at 13.737 s, and then silence. Host load (1 min) was 30.40 when the run began and 50.09 when it ended, from other worktrees' builds; the guest booted after twelve of the other guests had ended, besidevirt_el1_smp, the other 8-CPU TCG guest, which passed 11 s before this verdict. Alone a minute later at load 52.19 (r3-01b) it passed in 9 s, that job's line 0.4 s of guest time after the one before it; in the same group at load 24.75 (r3-09) it passed. It is a hang ceiling and not a line this change reads: the wait it stalled in isjudge_virt_job's, which this branch does not touch, and no harness wait can reach into the guest. ByCLAUDE.mda red seen only under load is a defect all the same. It ismain's, andissues/a-counters-read-under-host-load-can-go-silent-for-15-s.mdalready recorded it once, onvirt_el1_smp; that file gains this occurrence here (f9006e888), with the line, the load, the wait, what differs from the first, and what is and is not known.The negative controls
Round 3, the marker withheld at each of the two sites the review named: an uncommitted patch to the kernel each, posted below as a comment, applied with
git apply --check, run once and reversed,r3-04showing the tree clean after.r3-02sys_rebootrefuses without its recordFAIL virt_reboot_refused_without_psci: STALLED: waiting for the kernel's refusal of the reboot — it went quiet, with===TEST_END reboot exit=1===on the consoler3-03FAIL acpi_power_button: STALLED: waiting for the kernel's record of the mode it was handed over in — it went quiet, with the server's armed line on the consoleEach shows the new wait is the one that reads the record, and that it is bounded: the other marker came, the record did not, and the test ended by name after the guest's silence. Neither stages the race itself: a kernel record cannot be made late on the wire without changing
klogd.Round 2, at
50947bc56, staged the race for the short stop inacpiserver, and stands for the first row: the armed line said 3 s late turned the reverted fix red with the recorded failure word for word (r2-03, exit 1) and the fix green (r2-04, exit 0); the line withheld wasSTALLED: waiting for the ACPI server arming(r2-05, exit 1).r2-08ran the three x86-64 power guests five times in a row at loads 49 to 63, each exit 0.Why these stay guest tests
No test's behaviour changed: each judges what it judged, on QEMU's own machine. Only what the harness waits for before it reads moved.
Unsure of
klogdtrails, which is the review's argument for the two it named.virt_failed_ap_leaves_no_hole's "no CPU past the failed one joined", andmust_be_cleanon a boot log. That is a possible false green, not this criterion's false red, and is left.started netstackbeside netstack's own lines iniommu_virtio_platform, rest onlogkeeperwriting in stamp order; left, as round 1's review judged that order sound.drain_orderedskips a shard whose head is not yet committed, so their order on the wire is the stamps' only once both are committed. No site read here rests on it that I found.logkeeperanswering the supervisor's flush inside its bound; where it does not, the supervisor says so and stops without it.🤖 Generated with Claude Code
https://claude.ai/code/session_01RvnWQFcMuGqTHYhvSnTe8A