Stage 3 follow-on: message-arrival eligibility hook + trampoline-blind switch-storm fix (FABRIC-3.md §XXVIII.2)
Implements the message-arrival eligibility signal FABRIC-3.md §XXVIII.1 left open (has_work per-slot flag, set via new SWITCH-MARK-WORK primitive from MSG-SEND) so an idle VM never becomes a switch target purely by waiting out the readiness threshold. Also root-causes and fixes a second, independent switch-storm: the tick's "who is current" check used vm_log_attributed_vm(), which can't see a VM parked in switch.c's own raw trampoline. Replaced with a dedicated g_switch_current_vm tracked by the switch mechanism itself, and moved target-slot eligibility reset to the switch decision point instead of relying on ISR polling to observe a window that can be only a few instructions wide. Verified live on all 3 architectures: clean boot to zuse@Hera] ok>, live cross-VM message dispatch, and (since a quiet log looks identical to a livelocked storm once the DoE probe is gone) confirmed genuine REPL liveness via QMP send-key + screendump on aarch64/riscv64, not log inspection alone. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BWpNjdwPtFLuVLaAq44L9K
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
862d7d9c48
commit
66beae7fd4
+90
@@ -3804,3 +3804,93 @@ counter/flag, written at the FORTH-side message-enqueue site, read the same ISR-
|
||||
readiness already is) but not yet implemented -- superseded in priority by this stack-ownership
|
||||
finding, which had to be fixed first. Revisit once this correction lands.
|
||||
|
||||
## XXVIII.2 -- Message-arrival eligibility hook implemented; a second, independent switch-storm
|
||||
root cause found and fixed in the same pass (2026-09-14)
|
||||
|
||||
**Picks up the "still open" item from §XXVIII.1's close: the message-arrival-hook eligibility
|
||||
signal.** Added `has_work`, a per-slot flag beside the existing `readiness` counter in
|
||||
`sk_switch_slot_entry_t` (`capsule_vm_switch_signal.c`). `sk_vm_switch_signal_mark_work(vm_id)`
|
||||
sets it; `sk_vm_switch_signal_tick()`'s pending-switch decision now requires both
|
||||
`readiness >= SK_SWITCH_READINESS_THRESHOLD` *and* `has_work`, and clears it (alongside
|
||||
`readiness`) the instant a slot becomes current, same as before. Wired end to end: `MSG-SEND`
|
||||
(`capsules/common/messaging.4th`, block 5021) now calls a new `SWITCH-MARK-WORK ( caddr u -- )`
|
||||
primitive (`mama_word_switch_mark_work`, `mama_forth_words.c`) with the message's `to` name,
|
||||
captured into a new `MSG-SEND-TO-IDX` variable before the existing `Q.SLOT`/`MSG-ALLOC` sequence
|
||||
consumes the stack. `SWITCH-MARK-WORK` resolves the name via
|
||||
`capsule_vm_find_by_name_nocase()` and silently no-ops on an unknown or malformed name --
|
||||
deliberate, since this fires on every message send, not a user-facing command. Registered in
|
||||
all four of `mama_forth_words.c`'s word-table blocks (`register_mama_forth_words()` x2,
|
||||
`register_child_vm_words()`), matching how every other VM-scoped primitive in that file is
|
||||
exposed. A VM that has never been sent anything therefore never becomes a switch target purely
|
||||
by sitting idle past the readiness threshold -- closes the "wasteful but no longer corrupting"
|
||||
bounce §XXVIII.1 flagged as its own still-open item.
|
||||
|
||||
**While instrumenting this for verification, a second, wholly independent switch-storm root
|
||||
cause turned up -- not a regression of §XXVIII.1's fix, a different bug in the same
|
||||
neighborhood.** `sk_vm_switch_signal_tick()`'s "who is current" check used
|
||||
`vm_log_attributed_vm()` (`vm_core.c`) -- the wrong source. That function tracks nested
|
||||
`vm_interpret()` calls for log-source attribution and has no way to know a VM is running while
|
||||
parked inside `switch.c`'s own raw-C trampoline (`sk_vm_switch_entry()`), which never itself
|
||||
calls `vm_interpret()`. A VM whose only production behavior is that trampoline was therefore
|
||||
never seen as "current," so its own `readiness`/`has_work` state below never reset by the
|
||||
current-slot branch of `tick()` -- readiness re-armed on the very next timer tick, forever, once
|
||||
nothing else happened to independently move attribution around it. Found live: a switch storm,
|
||||
QEMU pinned near 100% CPU, serial log frozen solid.
|
||||
|
||||
**Fix: track "who is physically running" inside the switch mechanism itself, independent of
|
||||
`vm_interpret()`'s own bookkeeping.** New `g_switch_current_vm` (`switch.c`), exposed via
|
||||
`sk_vm_switch_current_vm()`/`sk_vm_switch_set_current()` (`switch.h`) -- written unconditionally
|
||||
by `sk_vm_context_switch()` on both the switch-out and the eventual resume, the same
|
||||
single-writer-mainline discipline every other piece of Stage 2/3 state already follows. Seeded
|
||||
once at boot (`kernel_main.c`, right where Hera's slot registers) since she is genuinely running
|
||||
before any switch has ever happened and there is no other event to hang the initial value on.
|
||||
`sk_vm_switch_signal_tick()` now reads this instead of `vm_log_attributed_vm()`. An earlier
|
||||
attempt at fixing this by calling `vm_interpret(self, "MSG-TICK")` from inside the trampoline
|
||||
itself was tried and reverted -- it traded the switch-storm for a different, unrelated hang (a
|
||||
stall in `sk_repl_headless_wait()`'s WIREBIND-attachment wait, not root-caused, not worth
|
||||
chasing once the real fix was found) -- worth recording so it isn't retried.
|
||||
|
||||
**A third bug, same root cause (eligibility state read too late), fixed alongside the other
|
||||
two:** `sk_vm_switch_signal_note_switch_performed()` reset a target slot's `readiness`/`has_work`
|
||||
by polling `tick()`'s current-slot branch, which depends on a real timer tick landing while that
|
||||
VM is genuinely "current" -- a window that can be as small as a few instructions for a VM whose
|
||||
only behavior is the placeholder trampoline's immediate yield-back, too narrow to reliably catch.
|
||||
Fixed by taking a `target_id` parameter (call site: `vm_core.c`'s Stage 3 checkpoint in
|
||||
`execute_colon_word()`) and resetting that slot's `readiness`/`has_work` directly, at the switch
|
||||
decision point, deterministically -- correctness no longer depends on ISR timing at all.
|
||||
`current_slot`/`sk_vm_switch_current_vm()` remain useful for the DoE-observable columns and for
|
||||
excluding the running slot from readiness accrual, but are no longer load-bearing for storm
|
||||
prevention.
|
||||
|
||||
**Verification, run live by Claude Sonnet 5 at Bob's request, per this project's
|
||||
write-probe-capture-revert discipline minus the probe (the temporary `HB-ON` DoE trigger from
|
||||
§XXVIII.1 stays reverted; this pass verified without it):** all three architectures rebuilt
|
||||
clean (`make -f Makefile.starkernel ARCH=<arch> clean qemu`, foreground, one at a time, prior
|
||||
instance fully torn down including leaked `tail -f` helper processes before the next launch) and
|
||||
boot to `[zuse@Hera] ok>` -- Zuse auto-authenticates from the attached thumbdrive on every arch,
|
||||
matching the identity-lockdown work from §XI. Live cross-VM message dispatch observed
|
||||
unprompted (`Stadium: dispatch cell=42634 behaviour=DELIVER msg_idx=0`, attributed to
|
||||
`[zuse@Artemis]`), confirming the eligibility mechanism is genuinely exercised, not just present
|
||||
in the binary. **Explicitly called out because it matters for how this was checked:** with the
|
||||
DoE probe gone, a healthy idle REPL and a livelocked storm produce the identical symptom -- a
|
||||
quiet serial log -- so log growth alone was not trusted as proof of health on aarch64/riscv64
|
||||
after a quiet stretch. Verified instead via QMP (`send-key` typing `1 1 + . ` followed by `ret`,
|
||||
then `screendump`) against the running instance's own QMP socket, confirming a real computed
|
||||
`2` appended to the log on both architectures -- the same headless-input technique this
|
||||
project's memory already recommends for exactly this class of ambiguity. All three
|
||||
architectures: clean build, clean boot, live messaging, correct computation, zero PANIC/assertion
|
||||
output, no leaked QEMU or helper processes after teardown.
|
||||
|
||||
**Found live, flagged, deliberately not fixed (not requested, not this pass's scope):**
|
||||
`VM-EXEC: ERROR in Artemis` prints deterministically on every boot, on all three architectures,
|
||||
immediately after Artemis's own birth completes and just before Zuse's thumbdrive
|
||||
identity-confirmation line -- consistent enough across arches to be a real, reproducible
|
||||
condition rather than timing noise, but boot continues cleanly past it every time. Worth
|
||||
someone's attention in a future pass; `mama_word_vm_exec()`'s error-report path
|
||||
(`mama_forth_words.c` ~line 781) is where the message originates, but the actual failing
|
||||
`VM-EXEC` call site and cause were not traced this pass.
|
||||
|
||||
**Still open:** the MSG-TICK/Stage-3-switch dual-ownership rough edge §XXVIII itself flagged
|
||||
(both mechanisms can independently move control between the same VMs) is unchanged by this
|
||||
pass. Stage 4 (WIREBIND-scope extension) remains a ratified-decision-only step, untouched.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user