Repository navigation
The machine's stop keeps its gate and waits for no thread - #1241
Conversation
141a667 to
5524159
Compare
Review of #1241 at 5524159Architecture. The split is right.
The two claims
What else a straggling thread can do after the stopA straggler here is a thread already inside a syscall when the stop begins, or one parked then and woken later. Once
The kept stopped bandKeep it.
The loom model and its control
Overlap and landing
The
|
| Run | Revision checked out | Result |
|---|---|---|
| 43027396712 | 422915e | red: DOUBLE PANIC, data abort from a lower EL |
| 43026252506 | 34cb4c9 | green |
| both experiment runs | 1314b4b and 3725bfc, whose parent chain starts at 422915e | red, the same panic |
| 43030668596 | 8502f16 | green |
The record's two claims about revisions are correct. It leaves out one fact: 34cb4c9 descends from 422915e and adds kernel changes (#1204: syscall/ipc, object/handle, loader/start.rs). Every red ran on 422915e's code and every green on later code. So the evidence cannot tell an intermittent defect from one fixed in between. The PR body's "pre-existing flake on main" is unsupported (suggestion 7).
Evidence
- Guest run. The guest log checks out 8502f16… and reports "15 passed, 15 total".
git diff 8502f16d7 552415919 -- . ':!issues'is empty. - Host checks. They ran at 141a667, which differs from the head only in the
virt_mask_windowsrecord. The doorbell control reds with its registered message. - The one host failure. The llvm failure in the build-system tests is the recorded macOS defect, in files this PR does not touch.
- The new row.
machine_shutdown_with_a_thread_spinningclaims only that the stop completes. It is the one row where a CPU must answer the demand while running a thread in Ring 3.
BLOCKERS
kernel/src/quiesce.rs:7–9: "a parked one stays parked" is false. This is the module's central contract. A thread parked when the stop begins can be woken by a deadline, a device interrupt or another straggler's post. It then runs the rest of its syscall in Ring 0 and is banded only at its boundary. The PR's own Risks section says so, and the mark that used to keep parked threads parked is what this PR removes. A reader trusting the header would rule out exactly the straggler cases analysed above. Replace it with something like: "A thread inside a syscall, or parked in one and woken later, runs on to the boundary, and what it logs may follow the last word."kernel/src/process.rs:779: "…so a machine stop waits for every record and release here" is now false. The stop waits for no thread, and this PR deleted thenote_progressbelow this line that made the clause true. Delete the clause.kernel/src/log/console.rs:18–20: "A holder the stop did not stop within its budget, which the stop's record names, may still write over it." is stale. There is no budget and no record any more.stop()now returns only after every other CPU has passed through Ring 0, so the UART's holder, which writes from Ring 3, cannot write over the stop's wire. Delete the sentence.issues/the-kernel-is-small-interrupts-post-and-threads-wait.md:361is stale. The inventory row covering firmware tables, ACPI mode, reset and power-off, the machine's stop and what the loader named reads "the rest below". That pointed at the stop's design-change bullet, which this PR deletes, and the loader row below is marked Done. The design now keeps the stop's gate (the services page). Change the cell to say so, for example "yes for the tables, theacpirow, power and the stop's gate".
SUGGESTIONS
kernel/pure/sched/cpu.rs:1244–1247. The comment onhandle_retire's home arm says a word naming this CPU "means anAdoptis on its way here". For a banded task it means the band, and this PR's "a retire finds it at home and leaves it there" rests on exactly this arm. The gap predates this PR, but adding one clause would make the reliance visible.kernel/src/scheduler.rs:141. "if either mark it can carry" is out of date: the stop is no longer a mark on the thread.kernel/src/sched/driver.rs:722–723. "whatever a CPU returns to Ring 3 after its own read what this caller wrote" is garbled. Suggested: "a thread a CPU returns to Ring 3 after that pass reads what the caller wrote before the call".kernel/src/arch/x86_64/pio.rs:143. "Take every row's ports back" no longer matches the code: the function now only mints the proof.kernel/src/power.rs:44. "which waits on userland's stop" should now say it waits for every other CPU's pass. The conclusion still holds: a CPU stuck with interrupts masked blocks the stop, which then panics afterDEAF_CPU.- The log case above. If blocker 1's wording does not cover it, add it to the Risks. Then a future aarch64 PSCI-row red with an
exit:line after the last word is recognized as this design's residual rather than chased. - The
virt_mask_windowsrecord. Add that 34cb4c9 descends from 422915e with kernel changes, and that the first step is a repeat at 422915e and at current main.
For the orchestrator: claims in the PR body and commit message
- "pre-existing flake" is unsupported (see the
virt_mask_windowssection above). - "Two boots, for the T14 and the AMD bench": only the T14 run is queued. The body should say which boots actually ran.
- The whole PR is +289 −1205 at the head, not +286.
- The commit message's "It established nothing the supervisor's orderly stop does not" should be corrected along with blocker 1.
What the T14 readback must show
- The build line names 5524159 and says it is clean.
metalcase/machine_reboot:- the supervisor's stop line and logkeeper's stop mark are on the volume;
Rebooting.is on the console;- the machine comes back, and the next loader pass finds no panic record.
- In particular, there is no
sched: cpuN began no pass in … of being kicked for one. This is the first run of the new acknowledgement on metal, across all the T14's CPUs, on a reboot path that had no acknowledgement before.
benchcase:- all 16 osbench groups and
test_rs_lan_leasepass; - the boot ends in a clean reboot with the stop mark;
- the sched, ipc and tail groups are within the existing 2× guard of the T14's recorded values.
- If any of those groups is slower than its recorded value, investigate it, since this PR removes a fence from the park path. A missing gain is not a rejection.
- all 16 osbench groups and
SEND BACK. The four blockers are text fixes, plus the rebase described above; no code change is required. Once they are fixed, the verdict is LAND, pending the T14 readback above.
…request does The record asked which of two readings of the design holds: a parent asks its children to quit over its own protocol and the kernel keeps only the kill, or the kernel's walk of the subtree is the mechanism the interrupt, hang-up and terminate events need. The orchestrator decided the second. Why the walk stays: whether a process listens and whether it is claimed for its end are checked and acted on as one act under one lock, which the process-lifecycle model checks, so no read comes between them; a hung or deaf process in the middle of a tree cannot shield what is below it, since the walk reaches each process itself; and the userland alternative needs a listener in every process and a relay in three runtimes to carry a request down (the orchestrator's analysis; not re-derived here). The authority page now states it: a holder of a child's handle may ask its subtree to quit with a reason, a listener receives it as an event, one that never listened or has not taken the last request is ended, and the kernel never asks on its own. "Quitting gracefully is the parent's job" is replaced by that last clause, which is the part of it that was true. The sessions page read as if a sign-out offered every app the chance to finish, and as if a second request ended a process that ignored the first. What happens: the first ask ends at once every app that never listened; a second request ends a process that has not yet taken the first and asks again one that has taken it; a process that took a request and carries on is ended only by a force. issues/the-kernel-decides-who-is-asked-to-quit-and-who-is-killed.md is deleted and its one citation, in the kernel inventory's list of unresolved design changes, with it. The durable rule already stands at the site that owns it, kernel/proclife/src/quit.rs's header. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
The kernel's stop did two things: it closed Ring 3 to every thread but its caller, and it swept the process table, marked parked threads and waited inside a budget until every other thread had parked, banded or exited, then wrote a record of the count. The first is the kernel's alone: its last sequence takes ports back, settles what programs left in flight and cuts power, and no program can vouch that nothing runs in Ring 3 across it. The second kept a thread parked when the stop began from running the rest of its syscall, since a wake put a marked thread in the band unrun, and gave a thread running in a syscall a budget to reach its boundary that it could outlast. Neither is something the machine's end needs: what had to finish is flushed, synced and ended before the supervisor asks. A thread inside a syscall, or parked in one and woken later, now runs on to its boundary, and what it logs may follow the last word. The gate stays. `quiesce::stop` names its caller, then demands a pass of every other CPU and returns once each has begun one (`Doorbell:: demand_pass`, `driver::pass_on_every_other_cpu`, shared by both architectures): a pass runs in Ring 0, and whatever a CPU returns to Ring 3 after it reads the stop begun and is banded at that boundary. A CPU that begins no pass inside `DEAF_CPU` is wedged, a panic. This answer from Ring 0 replaces the user-half shootdown `pio::take_back` ran for the same reason, and the `Stop` shootdown origin goes with it. Removed: the sweep, its watch and budget, `note_progress` and the `SeqCst` fence it put on every park, band and exit; the watch `Gate` and its loom model and control; `TaskShared`'s `STOP` mark with `stop_if_blocked`, `stop_pending` and `mark_stop`, and `CpuSched::place`'s arm that banded a woken or adopted stopped task (a banded task's word reads `Ready` on its own CPU, so no wake claims it and a retire finds it at home and leaves it); the `stop:` record and its parser in `toyos-quiesce`, which keeps the supervisor's bounds; the `stop-budget-spent` actuator. The band itself stays: a task stopped at its boundary still needs a container. Scheduler functions changed: `CpuSched::place`, `keep_stopped`, `handle_retire` (a comment), `SchedPass::dispose_stop`, `invariants::check_cpu`, `Doorbell::begin_pass` (and new `demand_pass`, `pass_demanded`), `driver::pass`, `driver::pass_block`, `driver::ring_all_but_self` (now `pass_on_every_other_cpu`). Tests: `machine_shutdown_short_stop` judged the record's shortfall, which no longer exists; it becomes `machine_shutdown_with_a_thread_spinning`, two CPUs with a spinner in Ring 3 when the stop begins, judged by a clean power-off. `virt_smp` no longer reads a record's CPU count, and the USB ordering row orders usbd's end before the kernel's last word. The new loom model `a_pass_that_answers_a_demand_reads_what_its_demander_wrote` reds under the existing `doorbell-kick-relaxed` control, and `a_retire_for_a_stopped_task_leaves_it_in_the_band` replaces the unit tests of the removed mark. The question record asked whether the kernel's sweep held anything the supervisor's stop does not, on the premise that the design names only the power-off as the kernel's. The design never named the sweep, but the Ring 3 gate is not the supervisor's to provide; that is what the record's exit wanted written at the stop's header, and it is. The record is deleted, with six records about the removed wait, record, staging and stop routes; the services page says the kernel waits for no program once the stop is asked. An unrelated aarch64 defect met on the way, `virt_mask_windows` red on main itself, is recorded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5524159 to
954b57d
Compare
Follow-up review of #1241 at 954b57dScope. This round checks only the top commit 954b57d against 5524159: the four blockers, the rebase onto #1218 (dd0ceb7, which sits on main 67713cb), suggestions 1–7, and the PR body and commit message. It reopens nothing settled in the previous review. Architecture. Unchanged since the previous round, and still right. The gate stays and the wait goes. The shared doorbell demand is the Ring 0 acknowledgement, and the band is the stopped thread's container. How the rebase was checked
Blockers from the previous round: all resolved
Rebase resolution: correct
Suggestions 1–7: all taken, all accurate
PR body and commit messageEach correction requested last round has been made:
Line counts against dd0ceb7 match the table exactly:
Three small inaccuracies remain, none blocking. They are for the orchestrator.
Re-verification evidence (d730bba37,
|
| Check | Result |
|---|---|
kernel-sched |
exit 0, 199 pass |
loom |
exit 0, 4 pass, including the new model |
control-doorbell |
exit 101, panics with the message build/ci.rs registers for it |
kernel-sim |
exit 0; the binaries add up to 69 |
| clippy | exit 0 in all 10 shapes; the kernel is checked in each |
| build-system | 384 pass, 1 red: the recorded macOS llvm test, in files this PR does not touch |
This is enough for a rebase whose only code delta is comments and a constant.
Metal (pending)
The T14 readback staged at 5524159 must show what the previous review listed:
- a clean build line naming 5524159;
- the supervisor's stop line and logkeeper's stop mark on the volume;
Rebooting.and the machine's return;- no
sched: cpuN began no passpanic; - all 16 osbench groups and
test_rs_lan_leasepassing, with sched, ipc and tail inside the 2× guard. Investigate any slowdown against the recorded values.
BLOCKERS
None.
LAND, pending the T14 metalcase and benchcase readback above.
|
Orchestrator: follow-up review r2 was LAND pending the T14 readback. The judge at 5524159 ( |
…cks' spawn records in main's form Main moved under the branch in three places this branch also changes: #1241 renamed `machine_shutdown_short_stop` to `machine_shutdown_with_a_thread_spinning`, which is now that name's `MACHINE_TESTS` entry; #1238 changed `virt_user_mode`'s arm, which this branch moved into a function and which now carries the change; and #1238 changed the kernel's spawn record to `spawn: pid=N (total=Xms) <path>` and the supervisor's to `started logkeeper pid=N`, which the one-clock check's planted log now writes beside the T14's first record. #1238's readers no longer read the older form, so the two bench readbacks this branch carries as fixtures, written before it, read as a boot with no record of logkeeper's spawn and a job with no spawn record. Their three lines each are rewritten in the form the kernel and the supervisor now write, with the same pids and times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cks' spawn records in main's form Main moved under the branch in three places this branch also changes: #1241 renamed `machine_shutdown_short_stop` to `machine_shutdown_with_a_thread_spinning`, which is now that name's `MACHINE_TESTS` entry; #1238 changed `virt_user_mode`'s arm, which this branch moved into a function and which now carries the change; and #1238 changed the kernel's spawn record to `spawn: pid=N (total=Xms) <path>` and the supervisor's to `started logkeeper pid=N`, which the one-clock check's planted log now writes beside the T14's first record. #1238's readers no longer read the older form, so the two bench readbacks this branch carries as fixtures, written before it, read as a boot with no record of logkeeper's spawn and a job with no spawn record. Their three lines each are rewritten in the form the kernel and the supervisor now write, with the same pids and times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o longer uses #1241 deleted the watch's gate model and this change deleted the two-submitter model. Those were `loom_watch`'s two uses of `CPU1`, so after both the import is unused, and `cargo clippy -p kernel-loom --all-targets` refuses it. The rebase's other resolutions are in the commits it replays: `resolve` and `ThisProcess` look up through #1246's `with_handles`, and the watch imports `poll::Via` beside main's `Arc` and `CellLock`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1218 has merged. This PR is one commit, 954b57d, whose parent dd0ceb7 is #1218's commit on main. GitHub's compare reads it one commit ahead of main, and it merges cleanly onto main e558cc1. The PR's commit list may still show dd0ceb7 from its last sync.
Problem
The kernel's stop did two jobs. It closed Ring 3 to every thread but the one that asked. It also swept the process table and waited, inside a 2 s budget, for every other thread to park, band or exit, then logged a
stop:record of the count.The wait did two things:
It cost these:
SeqCstfence on every park, band and exit;STOPmark with three routes into the band;Gatewith its own loom model;Neither of its effects is something the machine's end needs. What has to finish is flushed, synced and ended before the supervisor asks.
Result
quiesce::stopnames its caller, demands a pass of every other CPU and returns once each has begun one.DEAF_CPUis wedged, and the kernel panics.Doorbell::demand_pass,driver::pass_on_every_other_cpu), so AArch64 and the reboot path get the same acknowledgement from every CPU.pio::take_backran for the same reason, so theStopshootdown origin is removed.note_progressand its fence on every park;Gate, with its loom model and itsgate-fence-offcontrol;TaskShared'sSTOP/stop_if_blocked/stop_pending/mark_stop;CpuSched::place's stop arm;stop:record and its parser (toyos-quiescekeeps the supervisor's bounds);stop-budget-spentactuator.dispose_stop. A banded task's word readsReadyon its own CPU, so no wake claims it. A retire falls tohandle_retire's home arm and leaves it there.Scheduler functions changed (wakeperf lands first)
CpuSched::place,CpuSched::keep_stopped,CpuSched::handle_retire(a comment),SchedPass::dispose_stop,invariants::check_cpu,TaskShared::at_safe_point(doc only),Doorbell::begin_pass, newDoorbell::demand_pass/pass_demanded,driver::pass,driver::pass_block,driver::ring_all_but_self→pass_on_every_other_cpu.Lines
This PR's commit against its base dd0ceb7:
kernel/src(11 files)kernel/pure(5 files, tests included)kernel/loomandkernel/Cargo.tomllib/quiescetestsTests
machine_shutdown_with_a_thread_spinningreplacesmachine_shutdown_short_stop, which judged the record's shortfall that no longer exists.a_pass_that_answers_a_demand_reads_what_its_demander_wrotechecks that a CPU whose pass answered the demand reads the gate open. It reds under the existingdoorbell-kick-relaxedcontrol, which is now registered for it inbuild/ci.rs.a_demanded_pass_is_answered_by_the_next_pass_and_nothing_elsecovers the doorbell's new bit.a_retire_for_a_stopped_task_leaves_it_in_the_bandreplaces the unit tests of the removed mark.virt_smpno longer reads the record's CPU count.Evidence
Evidence branch: https://github.com/ToyOSOrg/evidence/tree/c88f1fa7da95900f3611da7bd9de9be3d12e7d25
Reused, and why it still holds
Guest rows. Run 43030668596 at 8502f16 passed 15 of 15:
machine_shutdown_with_a_thread_spinning,a_clean_stop_proves_its_image,the_log_lands_on_the_boot_stick;acpi_mediated_access,acpi_lock_given_back_on_one_cpu,acpi_supply_outlives_holder,acpi_press_is_one_sci;session_lifecycle;virt_smp,virt_el1_smp,virt_reboot,virt_reboot_refused_without_psci,virt_off_names_the_cpus_left_on,virt_fatal_halts_the_others_first,virt_mask_windows.The aarch64 rows ran because the change is in shared scheduler code.
Metal. The T14 run at 5524159 (below), whose code equals 8502f16's outside
issues/.Neither changes for this head.
git range-diffof 5524159 against this head shows four kinds of change in code:invalidation.rsconflict resolution (COUNT = 7, with nostopname);check_stack_canary→check_stackindriver::passanddriver::pass_block, andOwnedAllocinprocess.rs;issues/.None of these is on the stop path. The rebased head's compilation is covered by round 2's clippy shapes below.
First: did anything depend on the wait?
An experiment kernel kept the gate, swept nothing and waited for nothing (1314b4bcb, 3725bfcc8). Every stop row that built passed on it, in runs 43024839173 and 43026246044. Two rows were red:
machine_shutdown_short_stop, by design: the record always read zero.virt_mask_windows(Risks).Host checks, round 1 (141a667)
The code is the same as 5524159. Logs.
loom_sleeploom_watchdoorbell-kick-relaxedcontrolkernel-simtoyos-checks(the harness, including the changed record readers)toyos-quiescecargo test --lib(the build system)issues/the-llvm-environment-test-breaks-git-on-macos.md, inbuild/llvm.rsandbuild/sysroot.rs, which this PR does not touchHost checks, round 2 (this head, after the rebase)
At 954b57d. Logs.
cargo test -p kernel --lib --features sched-check sched::cargo test -p kernel-loom --test loom_sleep--features doorbell-kick-relaxed --test loom_sleep -- --exact a_pass_that_answers_a_demand_reads_what_its_demander_wrotecargo test -p kernel-sim-D warningswith the adopted lints: the library with and without its controls;x86_64-unknown-noneandaarch64-unknown-none-softfloat, each plain, withboot-actuators,test-actuators, withboot-actuatorsand with its instrumentscargo test --lib(the build system's issue gates read this round's records)Not run, and why: the full
--ci host. This change touches no build step, no other host member, crypto or the apps. The merge queue runs the full host checks.Metal: the T14 at 5524159, green
Two boots ran on the T14 only:
metalcaseat 18:19Z andbenchcaseat 18:26Z. No AMD bench run was made. Readbacks, judge and guard.Driver verdicts and boot facts:
toyos-metalread "passed" for both boots. That verdict requires:Boot: complete;logkeeper's stop mark on the volume;supervisor: build 55241591965afdae0033da2adc5fb30104c5c48b clean.Offline judge. Run from a clean detached checkout at 5524159 with
cargo test --test toyos-build -- --metal --metal-readback <dir> machine_reboot boot:benchcase: exit 0, 5 passed, 0 failed (judge.log).osbenchclock_holds_over_minutesi219_leaves_ulp_unarmedi219_carries_netstacks_linkmachine_rebootSystemIO 0xcf9 <- 0x06decodedAll 17 jobs of
benchcase(16osbenchgroups andtest_rs_lan_lease) andmetalcase's job ended with exit 0. The 4 timed numbers on this machine are within the 2x guard of their records: 0 past their record.osbench sched, ipc and tail against the four ToyOS T14 boots of the 2026-11-11 scoreboard at ad5db5a (osbench-guard.txt). One boot against four, with main moved in between, so this is a guard and not a measurement of the change. Of 27 rows:
pingpong.futex.cross_coreis past 2x the recorded median: 4,685 ns against 257.Four rows are 1–8 % worse than every recorded boot:
wake.storm.call.w4: 1.08x;wake.storm.call.w16: 1.05x;ipc.connect_accept: 1.01x;tail'scpu.clock: 4,189 against 4,190 MHz.The recorded spread of the two
wake.storm.callrows is 10 % and 8 %. This change adds no work to the wake or call path: it removes a load fromCpuSched::placeand a fence from the park, band and exit paths, andbegin_passclears one more bit in the same instruction. The sibling rows go the other way:wake.storm.call.w10.93x,wake.storm.drain.w10.93x,pingpong.futex.same_cpu0.95x. Investigated by that reading and not by a paired session; a paired base-and-head session would settle it.The other 22 rows are at 0.93x to 1.10x of the recorded median, within or below the recorded range.
What the T14 readback cannot show. The kernel writes its census and last word (
Rebooting.) afterlogkeeper's flush, to the UART alone, and the T14 bench captures no UART. Asched: cpuN began no passpanic in the stop would also land there. So its absence from the volume log establishes nothing, and that panic's own reboot after 5 s would also bring the machine back. The loader reads no crash record (issues/a-crash-record-survives-a-reset-outside-ram.md), so "no panic record at the next loader pass" is not observable either. What the T14 does show:The guest rows above, whose console is read, show the stop completing and its last word, including with a thread spinning in Ring 3 on another CPU.
Risks
SMI_CMDwrites are refused under their locks once the stop has begun.settlewaits out an act already in flight.exit:record can follow the last word. It would come from a straggler's teardown: a process whose last thread was already inprocess::leaveor its teardown when the stop began. That record can reach the UART after the last word. The aarch64 PSCI rows (ended_through_psci) red on any line after the last word, so a future red there with anexit:line is this design's residual. It was not seen in the 15/15 run or in either experiment run.virt_mask_windows(aarch64) red, cause unknown. It was red at main 422915e (43027396712) and on both experiment runs, which branched from 422915e. It was green at 34cb4c9 (43026252506) and at 8502f16. Every red ran 422915e's code and every green later code, and 34cb4c9 adds kernel changes (A move names the rights it delivers: a ring and a manifest's SysCap stay where they were given #1204). So whether it is intermittent or was fixed in between is not established. It is recorded, with the repeat that would tell, asissues/virt-mask-windows-finds-a-mask-with-no-hook-in-a-lower-el-data-abort.md.Review round 2
invalidation.rsis resolved against Every kernel stack sits above unmapped memory, and an overflow names its stack #1211 toCOUNT = 7, with nostopname and noStopentry. Both adjacent design-change bullets are deleted.process.rs's false clause is deleted;console.rs's stale sentence is deleted;handle_retire's home arm names the band;leave_user_if_due's doc;pass_on_every_other_cpu's doc;take_back's doc;reset_now's doc;exit:residual in Risks;virt_mask_windowsrecord's ancestry fact and first step.🤖 Generated with Claude Code