Repository navigation
A wake behind a busy home goes from its claimant straight to an idle CPU, once the home has saved it - #1274
A wake behind a busy home goes from its claimant straight to an idle CPU, once the home has saved it#1274Japabu wants to merge 7 commits into
Conversation
…ow it leaves - The wake-onto-busy record names the A/B owed on metal for #1274. It is counterbalanced as B,H,H,B,H,B,B,H on both benches, and comes with the idle-exit latency per sleep depth. The speed-up is not yet measured on metal. - The record of the busy-target wake says that a remote claim behind a busy home now goes straight to an idle CPU, and where the home's kick and forward remain. - A finding records the one window the prune leaves: a forward racing the pass that takes its home idle. It is inferred from the orderings, not observed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 of #1274 at
|
…ow it leaves - The wake-onto-busy record names the A/B owed on metal for #1274. It is counterbalanced as B,H,H,B,H,B,B,H on both benches, and comes with the idle-exit latency per sleep depth. The speed-up is not yet measured on metal. - The record of the busy-target wake says that a remote claim behind a busy home now goes straight to an idle CPU, and where the home's kick and forward remain. - A finding records the one window the prune leaves: a forward racing the pass that takes its home idle. It is inferred from the orderings, not observed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
15afe78 to
0ae65b7
Compare
Review r1 of #1274 found that a remote claimant could move a parked task while its home still stood on the task's kernel stack. The parking pass publishes the record before the switch that saves the context, so a claim in that window let another CPU resume a context nothing had saved. The orchestrator amended the approved design (blocker 1): a task may be sent on only after its home has switched away from it. - Each task has an on-CPU word (`TaskShared::on_cpu`). The CPU that dispatches the task sets it. The switch that leaves the task clears it with release once the outgoing stack pointer, the last of the context, is stored. That clear is inside `context_switch` on both architectures: a plain byte store on x86-64, which does not reorder a write with older writes (SDM Vol. 3A §11.2.2), and `STLRB` on AArch64. `RunToken` carries the word (`left_ptr`), and the CPU keeps the loaded task's beside its context. - A claimant that wins `Blocked -> WakeQueued` reads the word with acquire. If it is set, the wake goes home as `Msg::Wake`, whose next pass follows the switch, so that window keeps today's two hops. If it is clear, the claimant may send the task on. Nothing spins. - A woken task the CPU still stands on stays in its run queue (or dying list) for the pick, which sends on a loaded task its mask leaves out (`CpuSched::wake_parked`). The only such task is one a claim reached between its commit and its park (blocker 2). Through `place`, a mask that had moved away sent it to `hand_off`'s assert. - The claimant classes a boosted wake as RT, as its wake will (blocker 3). - What would queue behind a CPU's running task is one predicate (`Behind`), published for claimants and read by the home's own placement. A running corpse holds no band. - `ParkedRecord` loses its deadline, so the home's due set is the deadline's only home, and `take_parked` drops the task's entry unconditionally. - The comments the review lists, and the remaining mentions of the old park map, say what is true now. Checks the change rests on: - Unit tests: - `a_claim_before_the_homes_switch_goes_home` notifies between cpu0's `dispose_block` and `finish`, and cpu1's pass must not dispatch the task. - `a_task_woken_as_it_parks_with_its_mask_moved_away_is_sent_on_by_the_pick` covers blocker 2. - `a_boosted_wake_behind_a_fair_home_goes_home` covers blocker 3. - The test world now runs every pass's switch. - Loom: the model's context is a cell, and its switch saves it, clears the word and resumes the next. In `a_claim_sends_a_task_on_only_once_its_home_has_saved_it`, cpu1 claims while cpu0 commits, parks and switches, and then dispatches what it was sent. The model reaches a claim before the park, one before the switch and one after it. - Simulator: a pass's switch is its own step (`Step::Switch`), with interrupts masked, so other CPUs act between a pass and its switch. `claimed_behind_busy_home` stages a busy home, a waker on another CPU and a sleeper, and `a_claim_never_sends_on_a_context_its_home_has_not_saved` sweeps 1,000 seeded schedules of it clean. - The control `forward-ignores-on-cpu` has `on_cpu` answer `false`. It reds all three: - the unit test reads "cpu1 ran a task its home still stands on"; - loom reports "Causality violation: Concurrent write accesses to `UnsafeCell`" on the context; - the simulator reads "I11: resumed TaskKey(1) whose context was never saved", in 22 of 1,000 seeded schedules. - The round-1 control `parked-publish-relaxed` is removed. Once a claimant takes a record only after the on-CPU word's acquire, `commit_park`'s release is no longer the only edge that orders the record's write before the take. With it relaxed, the new model stays green, so the control no longer tests anything. The step split moved the simulator's schedules. The two old-protocol corpus traces were found again and shrunk: `old_steal_port` seed 5 for I8, and `old_migrate_kept_the_corpse` seed 135 for I14. The corpse-migration gate now sweeps 2,000 seeded schedules. Its defect is reached about once in 140 seeded schedules (22 of 3,000) and once in 3,000 PCT ones, against none of the 500 mixed ones it used to run. A boosted wake whose claimant can send it on itself never reaches `hand_off`, where that defect lives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…9252a0d1d6093: the on-CPU word's controls in three models, review r1's blockers, the simulator's reach; masked
Response to review r1 at
|
Review r2 of #1274 at 0ae65b7Scope: round 1's blockers and their fixes. I read the r2 commit, every file it changes, the r2 evidence, and the callers and entry paths named below. I ran no builds or tests. AssessmentThe amended mechanism has the right shape and stays small:
Nothing spins. The only new The kernel's placement of the clear does not hold. The clear publishes the context before the CPU has left the outgoing stack. The argument that nothing uses that stack in between assumes interrupts are masked, and in a default build they are not (B1). Blockers 2 and 3 are fixed correctly, and each test reds under its control. Round 2 is +673/−293 over 27 files. About 110 of cpu.rs's added lines are the three new unit tests and the test world's switch. BLOCKERSB1.
B2. Comments this round made false or unreadable.
SUGGESTIONS
On the questions asked
SEND BACK |
…stack Review r2 of #1274 found that both architectures cleared the word after storing the outgoing stack pointer but before moving onto the incoming stack. In a default build interrupts can be open there. Syscalls open them before dispatch, and `exit_to_user` opens them before `do_preempt`. So an interrupt taken at that boundary pushed its frame onto the outgoing stack after a claimant could already have sent the task on and resumed it. The comments claimed interrupts were masked, which holds only in the `mask-windows` build. - The clear now follows `mov rsp, rsi` and `mov sp, x1`. Every save still precedes the release, and nothing this CPU does afterwards can reach the old stack, whatever is masked. On x86-64 the clear stays inside `switch_save!`, where the witness build's `call` cannot clobber `rdx`. - Each comment states that one fact. The "every context is saved masked" comments in both `Hw::switch` bodies now name the `mask-windows` build they describe. - The simulator clears the word by key once its switch step has run (`TaskShared::left_cpu`, `protocol-port`), so it dereferences no raw pointer, as its crate note and `RunToken`'s say. That note now counts four exemptions, counting round 1's `Vm::parked_record`. - `Due::prune`'s note says that a park ended after its read leaves the deadline armed. The corpse gate's note keeps only the rate that sizes its sweep. The simulator's switch step is described as one step, not as masked. `TaskShared::dispatched` names the edge that carries the set to a claimant. - Rule 2's clause for a home with a queued task of the wake's band, which #1274 does not implement, has a record with its exit tied to the owed A/B: `issues/a-claimed-wake-ignores-a-queued-task-of-its-band-at-home.md`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ddd127ed4591d: the on-CPU word cleared after the stack switch; masked
Response to review r2 at
|
Review r3 of #1274 at 37b8806Scope: review r2's findings only. I read 37b8806's diff, the files it touches, the author's response and the r3 evidence. I ran no builds or tests. B1: the clear after the stack switch — resolved
B2: the comments — resolved
|
…hat ends the park A parked task's record lived in its home CPU's park map, inside a CpuSched no other CPU may touch, so whoever won the claim that ends a park could only post Msg::Wake to the home and let its pass place the task. The record now travels with the claim: the parking pass puts it in a one-owner slot on the task's TaskShared (`ParkedSlot`, beside the mailbox's node slot in the scheduler core's one unsafe module) and only then moves the word Committing -> Blocked, whose release publishes it; the winner of Blocked -> WakeQueued takes it with that CAS's acquire. - `WaitTicket::commit` no longer parks: it checks the kill and a claim already made, and `CommittedTicket::park` publishes the record and moves the word. A claim made between the two took nothing, so the pass wakes the record it published (`Parked::WokenFirst`), as that claim's wake. - `Msg::Wake` carries the task's `Arc<TaskShared>`; `handle_wake`, `handle_retire` and the deadline take the record from the slot after their claim. - The home CPU keeps only its deadlines: one entry per task, the last timed park's, with the park's commit generation. An entry whose park another claim ended is dropped when it falls due, since `TaskShared::claim_deadline` claims only the park of its own generation, so it never ends a later wait. - The simulator finds parked tasks in their slots; the call-site commit shapes and their gates go, since a commit can no longer publish `Blocked` while the task runs. No placement changes yet: every claim still hands the wake to the home. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… an idle CPU The policy of the approved placement design, on stage 1's ownership: the winner of the claim that ends a park takes the record and, where the home's published running band (`CpuHandle::band`, new, published by each pass beside its load) is the task's own band, sends it as an `Adopt` to an idle CPU in its mask that sleeps unrung, nearest the home and in the shallowest state (`CpuHandles::idle_near`). Otherwise it hands the record back to the slot and posts `Msg::Wake` to the home, as before. A claimant on the home itself, and a killed task, go home. - `SchedMsg::send_wake` is the hook (`park::post_wake` by default); `Msg<X>` overrides it with `CpuHandles::send_claimed_wake`. - `Kicker` carries the clock and the trace, which a wake that hands a task on needs for its accounting and the simulator's checks; `Machine` keeps the rest. - The simulator tracks a wake that leaves the parked set in any step, and stamps the wait of a park sent straight into transit. - A home drops the deadlines of parks that have ended before it arms its timer (`Due::prune`), not only when they fall due. The last point is the policy suite's finding. On this policy without it, `the_two_cures_are_priced_against_the_pull_path` read one wake of a halted CPU for nothing in each of wakeup_storm(4,16) seeds 1, 5, 11 and 13 under the shipped balance path, where it requires none. In seed 1's trace cpu1 passed to idle at 19 ms and its timer fired at 55.75 ms with no kick or IPI in between: the timer had been armed for the deadline of a timed park that a remote claimant had already sent on, and the pass it woke found nothing to do. The suspected race between the timer and a kick did not occur. The waste was real and the forward caused it, so the home now discards an ended park's deadline before it plans a timer. With the change all 20 seeds read zero, and `a_park_sent_on_leaves_its_home_no_timer` is red without the prune. One window remains. A claimant that read the band of the home's last busy pass, and claims after the pass that takes the home idle has pruned, still leaves that timer armed. The window is the end of one pass, and the timer fires once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ntrol `loom_claimed_wake.rs` runs whole `CpuSched`s for a busy home and an idle CPU. In the first model a remote waker races the home's park, from the commit through the publishing `Committing -> Blocked`. Whichever side of the park the waker lands on, the task ends up in exactly one CPU's containers. A claim after the park takes the record and sends the task to the idle CPU. Loom checks the slot's write and take as cell accesses. The second model races the home's deadline against the remote wake for one park. The record is taken once, and the deadline's entry is gone whichever claim wins. Each model asserts that its exploration reached both outcomes. The control `parked-publish-relaxed` makes `commit_park`'s CAS `Relaxed`. Under it the first model reds with loom's "Causality violation: Concurrent write accesses to `UnsafeCell`". The second stays green, because its park happens before the waker is spawned. The control is declared in both manifests and listed in CI's CONTROLS. - The scaffolding for models that run a whole `CpuSched` (payload, silent `Hw`, owner, spawn) moves from `loom_retire.rs` to `sched_model`, so both files use one copy. - New pure tests: a wake whose mask admits no idle CPU goes home and leaves the sleeper asleep. Wakes behind one busy home take one sleeper each, and the next goes home. Both red when the forward takes any sleeping CPU, ignoring the mask and the ring. - `TaskShared::peek_parked` and the slot's `peek` become `unsafe fn` for the simulator alone (`protocol-port`). They hand out a reference into a cell that another thread could take from, and only a caller that runs every CPU on one thread can promise it does not. The simulator reads them through `Vm::parked_record`. The two unit tests that peeked now rely on the next pass's take, which fails if the record is missing. - A due entry is a named struct, and comments that said every claim posts `Msg::Wake` now name the claimant's send. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow it leaves - The wake-onto-busy record names the A/B owed on metal for #1274. It is counterbalanced as B,H,H,B,H,B,B,H on both benches, and comes with the idle-exit latency per sleep depth. The speed-up is not yet measured on metal. - The record of the busy-target wake says that a remote claim behind a busy home now goes straight to an idle CPU, and where the home's kick and forward remain. - A finding records the one window the prune leaves: a forward racing the pass that takes its home idle. It is inferred from the orderings, not observed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- The home's retire fall-through: a word that names this CPU now also means a claimant sending the task on from here. The CPU that places it reads the sticky kill bit. - `send_claimed_wake` chooses as `idle_near` does, with no rule of its own. Comments only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r1 of #1274 found that a remote claimant could move a parked task while its home still stood on the task's kernel stack. The parking pass publishes the record before the switch that saves the context, so a claim in that window let another CPU resume a context nothing had saved. The orchestrator amended the approved design (blocker 1): a task may be sent on only after its home has switched away from it. - Each task has an on-CPU word (`TaskShared::on_cpu`). The CPU that dispatches the task sets it. The switch that leaves the task clears it with release once the outgoing stack pointer, the last of the context, is stored. That clear is inside `context_switch` on both architectures: a plain byte store on x86-64, which does not reorder a write with older writes (SDM Vol. 3A §11.2.2), and `STLRB` on AArch64. `RunToken` carries the word (`left_ptr`), and the CPU keeps the loaded task's beside its context. - A claimant that wins `Blocked -> WakeQueued` reads the word with acquire. If it is set, the wake goes home as `Msg::Wake`, whose next pass follows the switch, so that window keeps today's two hops. If it is clear, the claimant may send the task on. Nothing spins. - A woken task the CPU still stands on stays in its run queue (or dying list) for the pick, which sends on a loaded task its mask leaves out (`CpuSched::wake_parked`). The only such task is one a claim reached between its commit and its park (blocker 2). Through `place`, a mask that had moved away sent it to `hand_off`'s assert. - The claimant classes a boosted wake as RT, as its wake will (blocker 3). - What would queue behind a CPU's running task is one predicate (`Behind`), published for claimants and read by the home's own placement. A running corpse holds no band. - `ParkedRecord` loses its deadline, so the home's due set is the deadline's only home, and `take_parked` drops the task's entry unconditionally. - The comments the review lists, and the remaining mentions of the old park map, say what is true now. Checks the change rests on: - Unit tests: - `a_claim_before_the_homes_switch_goes_home` notifies between cpu0's `dispose_block` and `finish`, and cpu1's pass must not dispatch the task. - `a_task_woken_as_it_parks_with_its_mask_moved_away_is_sent_on_by_the_pick` covers blocker 2. - `a_boosted_wake_behind_a_fair_home_goes_home` covers blocker 3. - The test world now runs every pass's switch. - Loom: the model's context is a cell, and its switch saves it, clears the word and resumes the next. In `a_claim_sends_a_task_on_only_once_its_home_has_saved_it`, cpu1 claims while cpu0 commits, parks and switches, and then dispatches what it was sent. The model reaches a claim before the park, one before the switch and one after it. - Simulator: a pass's switch is its own step (`Step::Switch`), with interrupts masked, so other CPUs act between a pass and its switch. `claimed_behind_busy_home` stages a busy home, a waker on another CPU and a sleeper, and `a_claim_never_sends_on_a_context_its_home_has_not_saved` sweeps 1,000 seeded schedules of it clean. - The control `forward-ignores-on-cpu` has `on_cpu` answer `false`. It reds all three: - the unit test reads "cpu1 ran a task its home still stands on"; - loom reports "Causality violation: Concurrent write accesses to `UnsafeCell`" on the context; - the simulator reads "I11: resumed TaskKey(1) whose context was never saved", in 22 of 1,000 seeded schedules. - The round-1 control `parked-publish-relaxed` is removed. Once a claimant takes a record only after the on-CPU word's acquire, `commit_park`'s release is no longer the only edge that orders the record's write before the take. With it relaxed, the new model stays green, so the control no longer tests anything. The step split moved the simulator's schedules. The two old-protocol corpus traces were found again and shrunk: `old_steal_port` seed 5 for I8, and `old_migrate_kept_the_corpse` seed 135 for I14. The corpse-migration gate now sweeps 2,000 seeded schedules. Its defect is reached about once in 140 seeded schedules (22 of 3,000) and once in 3,000 PCT ones, against none of the 500 mixed ones it used to run. A boosted wake whose claimant can send it on itself never reaches `hand_off`, where that defect lives. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stack Review r2 of #1274 found that both architectures cleared the word after storing the outgoing stack pointer but before moving onto the incoming stack. In a default build interrupts can be open there. Syscalls open them before dispatch, and `exit_to_user` opens them before `do_preempt`. So an interrupt taken at that boundary pushed its frame onto the outgoing stack after a claimant could already have sent the task on and resumed it. The comments claimed interrupts were masked, which holds only in the `mask-windows` build. - The clear now follows `mov rsp, rsi` and `mov sp, x1`. Every save still precedes the release, and nothing this CPU does afterwards can reach the old stack, whatever is masked. On x86-64 the clear stays inside `switch_save!`, where the witness build's `call` cannot clobber `rdx`. - Each comment states that one fact. The "every context is saved masked" comments in both `Hw::switch` bodies now name the `mask-windows` build they describe. - The simulator clears the word by key once its switch step has run (`TaskShared::left_cpu`, `protocol-port`), so it dereferences no raw pointer, as its crate note and `RunToken`'s say. That note now counts four exemptions, counting round 1's `Vm::parked_record`. - `Due::prune`'s note says that a park ended after its read leaves the deadline armed. The corpse gate's note keeps only the rate that sizes its sweep. The simulator's switch step is described as one step, not as masked. `TaskShared::dispatched` names the edge that carries the set to a claimant. - Rule 2's clause for a home with a queued task of the wake's band, which #1274 does not implement, has a record with its exit tied to the owed A/B: `issues/a-claimed-wake-ignores-a-queued-task-of-its-band-at-home.md`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37b8806 to
e1325aa
Compare
…e8d861b63dd91: rebased onto 29c5f2dfa, composed with #1264; masked
Rebase onto main
|
Review r4 of #1274 at e1325aa (composition with #1264)Scope. This round reviews only how the rebase composes with #1264 (FSGSBASE). Architecture. Nothing moves. #1264 adds two kinds of state to the switch: a per-task record field ( CheckedEvery store into the outgoing record comes before the clear.
No reader assumes the task stayed on its CPU.
A forwarded task gets its own bases. If B's cache differs from T's saved value, B writes the register. If they are equal, B's register already holds T's value. Each The Evidence (
These match the author's composition note, and the note's three claims hold on inspection. Run 43173669251 was still queued at the head when I checked, so its result is not part of this review. FindingsNone. No blocker and no suggestion. #1264's exit asks for bases to read back unchanged "after its CPU has run another thread". That does not cover a thread moving between CPUs. Since each cache stores a value and the switch compares it against the task's own saved value, inspection settles this; no new test is needed. LAND |
Problem
A parked task's record lived in its home CPU's park map, inside a
CpuSchedthat no other CPU may touch. Whoever won the claim that ends apark could only post
Msg::Waketo the home and wait for the home's passto place the task. When the home was busy, a wake behind it cost three
things: an IPI to the home, a pass on the home, and a second hop and IPI to
the idle CPU that the home's
placethen forwarded it to.osbench'swake.onto_busyreads 1.74x Linux's on the T14 and 1.80x on the AMD bench(
issues/a-wake-onto-a-busy-cpu-costs-toyos-1-8-times-linuxs.md).Behaviour
one-owner slot on the task's
TaskShared(ParkedSlot, in thescheduler core's one unsafe module). Only then does it move the word
Committing -> Blocked, with release. The winner ofBlocked -> WakeQueuedtakes the record. A claim made between the commitand the park took nothing, so the parking pass wakes the record it
published, and keeps the task for its own pick.
on-CPU word (
TaskShared::on_cpu):context_switchon both architectures, once the context is saved andthe CPU has moved onto the incoming stack. Nothing this CPU does after
that, an interrupt included, touches the old stack.
set, the wake goes home as
Msg::Wake, which the home's next pass,after the switch, places. Nothing spins.
park's commit generation.
claim_deadlineclaims only the park of itsown generation. An entry whose park another claim ended is dropped
before the timer is armed (
Due::prune), or when it falls due.publishes which wakes would queue behind its running task
(
CpuHandle::behind), from the predicate its own placement reads(
CpuSched::behind). A claimant whose wake would queue there takes therecord and sends the task as an
Adoptto an idle CPU in its mask. Aboosted wake counts as RT, as its wake will lend it. The target CPU must
be asleep and not yet rung. It is chosen the way the home's own forward
chooses (
CpuHandles::idle_near): nearest the home, a free physical corefirst, then the CPU quickest to wake. Otherwise the claimant hands the
record back and posts
Msg::Wakehome, as before.stands on, a claim made on the home itself, and a killed task.
Msg::Wakecarries the task'sArc<TaskShared>.handle_wake,handle_retireand the deadline take the record from the slot aftertheir claim.
Decisions
and
Msg::Wakekept for the home case. Synchronous wakes are not inthis PR.
above. It closes a window in which another CPU could resume a context its
home had not saved, on a stack the home still stood on. Wakes claimed in
that window keep today's two hops.
open across a switch in a default build, so the clear must follow the
stack move rather than rely on masking.
thread's GS base into its record (
user_bases.gs), beside the FS base,at the top of
Hw::switch. Every write into the outgoing recordprecedes
context_switch, whose clear releases them in program order.Those writes are the FS and GS bases, the preempt count, the XSAVE area
and its
Leftrecord, the pushed registers and the stack pointer. Sothe GS base is part of the context the clear publishes, and a CPU that
resumes the task after reading the word clear loads the base its home
saved. On AArch64, x86-64: FSGSBASE is taken, and the switch carries each thread's FS and GS bases #1264 adds only an empty
UserBases.of the wake's band queued is not implemented. The claimant follows the
home's own
place, which weighs only what runs. The clause is recordedin
issues/a-claimed-wake-ignores-a-queued-task-of-its-band-at-home.md,with its exit tied to the owed A/B.
Behindis two bits, a fair wake's and an RT wake's, because the home'stwo rules read different facts. A running corpse holds neither.
Kickercarries the clock and the trace (now,trace), because aclaimant that hands a task on accounts for it and traces it.
Machinekeeps the rest.
SchedMsg::send_wakeis the hook:park::post_wakeby default, andCpuHandles::send_claimed_wakefor the kernel'sMsg<X>.TaskShared::peek_parkedis anunsafe fnfor the simulator alone(
protocol-port). Only a caller that runs every CPU on one thread canpromise that nobody takes from the slot meanwhile.
commit can no longer publish
Blockedwhile the task runs. Thesimulator's switch is now a step of its own, which the window above
needs.
a parked task lives or how a wake is placed.
The simulator's finding
On this policy, before the prune,
the_two_cures_are_priced_against_the_pull_pathread one wake of a haltedCPU for nothing in each of
wakeup_storm(4,16)seeds 1, 5, 11 and 13under the shipped balance path, where it requires none.
seed 1's trace refutes that. cpu1 passed to idle at 19 ms, and its timer
fired at 55.75 ms with no kick or IPI in between. The pass the timer woke
found nothing to do.
had already ended. The trace does not show which claim ended it. But
only a claimant that sends a park on leaves its home such a deadline, and
the first commit, which has no forward, kept the suite green. The waste
was real and the forward caused it.
before it plans a timer. All 20 seeds now read zero, and
a_park_sent_on_leaves_its_home_no_timerreds without the prune.One window remains, inferred from the orderings and not observed. A
claimant that read the busy word of the home's last pass, and claims after
the pass that takes the home idle has pruned, still leaves that timer
armed. It fires once. The on-CPU word does not close it, since that timer
is armed by a later pass than the switch the word orders. It is recorded,
with a candidate close, in
issues/a-wake-sent-on-as-its-home-goes-idle-can-leave-the-home-a-timer-for-nothing.md.Checks
Evidence, all masked:
Round 4
is at head
e1325aab6on main29c5f2dfa, rebased and composed withx86-64: FSGSBASE is taken, and the switch carries each thread's FS and GS bases #1264. It has the 14 clippy shapes,
cargo test -p kernel --lib(516),the simulator and its control, and
--ci host models(95 steps, allgreen).
Round 3
is at head
37b8806cc. It covers the clear moved after the stack switch,the clippy shapes on both targets, the kernel library, the simulator and
its control.
Round 2
is at
0ae65b768, on the same main3c5c55584. Its loom and--ci host modelsresults still hold: no model source changed since.Round 1.
Host: 14 clippy shapes,
-D warningswith the adopted lints, allexit 0:
actuators, boot actuators and instruments;
sched-check, and with controls;kernel-loomandkernel-sim, each alone and withforward-ignores-on-cpu.cargo test -p kernel --lib: 506 passed.every_model_control_is_runpasses.
Models:
cargo run -- --ci host modelsreads 95 steps, all green,exit 0. That is
kernel-loom,kernel-simand every control.The on-CPU word. The control
forward-ignores-on-cpumakeson_cpuanswerfalse. It reds three checks:a_claim_before_the_homes_switch_goes_homenotifies betweencpu0's
dispose_blockandfinish, and cpu1 must not dispatch thetask. Red: "cpu1 ran a task its home still stands on".
a_claim_sends_a_task_on_only_once_its_home_has_saved_it. Itscontext is a cell and its switch saves it, so a resume of an unsaved
one is a race. cpu1 claims while cpu0 commits, parks and switches, and
the model reaches a claim before the park, one before the switch and
one after it. Red: "Causality violation: Concurrent write accesses to
UnsafeCell".a_claim_never_sends_on_a_context_its_home_has_not_savedsweeps 1,000 seeded schedules of
claimed_behind_busy_home; the switchis now a step of its own. Red: "I11: resumed TaskKey(1) whose context
was never saved", in 22 of 1,000 schedules.
Blockers 2 and 3.
a_task_woken_as_it_parks_with_its_mask_moved_away_is_sent_on_by_the_pickreds at
hand_off's assert without the loaded-task rule.a_boosted_wake_behind_a_fair_home_goes_homereds without the boost.Round 1's tests and controls, still in force. Without the prune,
a_park_sent_on_leaves_its_home_no_timerreds. A forward that takes anysleeping CPU reds the mask test and the one-sleeper-each test. The loom
deadline-versus-wake model reaches both winners.
A control removed. Round 1's
parked-publish-relaxedis now green,so it is removed. Once a claimant takes the record only after the on-CPU
word's acquire,
commit_park's release is no longer the only edge thatorders the record's write before the take.
Simulator schedules moved by the step split. Two old-protocol corpus
traces were found again and shrunk. The corpse-migration gate now sweeps
2,000 seeded schedules; its defect is reached about once in 140.
Guest: run 43173669251
at
e1325aab6, after the rebase onto29c5f2dfa, is queued. Run43154897516
passed all ten rows at
37b8806cc, before x86-64: FSGSBASE is taken, and the switch carries each thread's FS and GS bases #1264.43143253873 and 43152223133 were cancelled.
vector_state_survives_a_switch,std_threads_count_the_cpus_and_give_back_ended_threads,affinity_pins_hold_on_every_cpu,libc_children, and the two sharedboots run whole,
testcases_members_exit_0andshared_debug_members_exit_0. Every switch changes, so the full sharedboot is worth its slot.
virt_fp_isolation,virt_smp,virt_jobs_at_el2andvirt_mask_windows.architectures share, and to both architectures'
context_switch. Therows cover parks and wakes across CPUs, affinity masks, processes
waiting on children, and a switch's state.
virt_mask_windowshas a known red on main(
issues/virt-mask-windows-finds-a-mask-with-no-hook-in-a-lower-el-data-abort.md).window's absence; the models do that.
e1325aab6is not in yet.Owed on metal
The benches are down, so this PR lands on review, host checks and guest
rows. The speed-up is not yet measured on metal. What is owed is
recorded in
issues/a-wake-onto-a-busy-cpu-costs-toyos-1-8-times-linuxs.md:bench. Four boots a side, in the order B,H,H,B,H,B,B,H. The rows are
osbench's
wake.onto_busy,pingpong.*.cross_core,ipc.reqrep.*,wake.storm.*andenergy.*. Main must readwake.onto_busyfaster onboth benches, with no other of these rows slower beyond its spread.
scan for an idle CPU finds none and the home's placement scans again. No
row may read slower there beyond its spread.
band queued. No
osbenchrow stages it yet, and its reading decides rule2's queued clause.
bench's
wake.onto_busyis split between the removed hop and the idleexit.
Risks
pass into the waker's context: an interrupt handler's, and a watch post's
under its list lock. Its cost there is unmeasured, and when no CPU is
idle the scan runs twice.
switched away from the task still takes both hops. How many wakes land
in that window on metal is unmeasured.
🤖 Generated with Claude Code