Stage O-B: Hera drains her own mail at the idle point, not on the ~1 s idle beat (FABRIC-3.9.0 §2.5)
- sk_vm_idle_point(may_drain_self): with no frame live and mail pending, Hera drains one message and returns to the poll loop. Readline passes n == 0 (the mid-edit gate), headless 1, the key read 0. - Removed sk_repl_idle()'s timer-paced drain of Hera's queue and the two flags that guarded it (g_mama_interpreting, g_idle_pump_active) -- the idle point's frames check already covers that re-entry hazard. Probe (reverted), all three ISAs: admission -> drain for mail to an idle Hera 0-1 beats (was up to 100); a message arriving mid-edit is held until the line is entered (2080-3587 beats), never printed into typed text; an event queued during a busy line drains when it ends; BLK-ATTACH at boot 1 beat. Acceptance passes; parity, dict_hash set and relay ledger unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
fe55038e9a
commit
ee4c401d1c
+27
-61
@@ -832,30 +832,13 @@ static void sk_cursor_show(void)
|
||||
console_fb_draw_cursor();
|
||||
}
|
||||
|
||||
/* Reentrancy guards for the kernel-Hermes drain pump inside sk_repl_idle().
|
||||
*
|
||||
* FABRIC-3.6.md Phase 4 (Stage E), 2026-09-22: this used to guard a
|
||||
* VM-EXEC "MSG-TICK" dispatch into every live child VM (FABRIC-2.md
|
||||
* Phase C, common:messaging.4th, now removed). The mechanism changed --
|
||||
* sk_repl_idle() now calls sk_hermes_drain_checkpoint() directly per VM,
|
||||
* not VM-EXEC -- but the reentrancy hazard these two flags exist for is
|
||||
* unchanged: drain_checkpoint() still calls vm_interpret() internally
|
||||
* (on the drained message's own payload), and sk_repl_idle() is itself
|
||||
* called from the blocking KEY/EXPECT/QUERY reads (sk_console_getkey()/
|
||||
* sk_console_readline(), which run *from inside* the executing VM's own
|
||||
* vm_interpret once a FORTH word reads input mid-line). vm_interpret()
|
||||
* is not reentrant -- it resets the VM's single input_buffer/input_pos/
|
||||
* input_length (vm_core.c) on entry. Calling it on mama at that point
|
||||
* would re-enter her interpreter mid-parse and silently truncate the
|
||||
* rest of the line. Two flags keep the pump off every path that could
|
||||
* re-enter an interpreter:
|
||||
* - g_mama_interpreting: set while Hera is executing a dispatched line, so
|
||||
* the pump defers to the next safe (prompt) boundary.
|
||||
* - g_idle_pump_active: belt-and-suspenders; stops recursive re-entry
|
||||
* from inside the pump's own drain call.
|
||||
*/
|
||||
static int g_mama_interpreting; /* zero-initialized (BSS) */
|
||||
static int g_idle_pump_active; /* zero-initialized (BSS) */
|
||||
/* FABRIC-3.9.8 Stage O-B (FABRIC-3.9.0.md §2.5): the reentrancy flags
|
||||
* g_mama_interpreting / g_idle_pump_active that used to live here guarded
|
||||
* this file's timer-paced drain of Hera's own mail inside sk_repl_idle().
|
||||
* That drain moved to sk_vm_idle_point() (switch.c), which acts only when
|
||||
* Hera has no frame at all -- not mid-line, not inside KEY/EXPECT/QUERY,
|
||||
* not inside a drain -- so the hazard the flags existed for (re-entering
|
||||
* her interpreter mid-parse) cannot arise there, and the flags are gone. */
|
||||
|
||||
/* FABRIC-3.md SXXII (2026-09-12) recorded a real O(N) bottleneck here --
|
||||
* the messaging pump below used to walk the entire live-VM registry
|
||||
@@ -1174,23 +1157,6 @@ static void sk_repl_idle(VM *active_vm)
|
||||
* same round-robin way the old MSG-TICK dispatch was (SK_MSG_PUMP_
|
||||
* BATCH below), kept for the same reason: bounding this to O(K)
|
||||
* regardless of total VM count, not O(N) every beat. */
|
||||
VM *mama = (VM *)sk_get_mama_vm();
|
||||
|
||||
/* Reentrancy guard: never run the pump while mama is mid-interpret
|
||||
* (a dispatched line, or recursively from within the pump's own
|
||||
* vm_interpret). vm_interpret() clobbers the VM's single input
|
||||
* buffer, so re-entering it here while KEY/EXPECT/QUERY blocks inside
|
||||
* a live parse truncates the rest of that line. Deferring the MSG-TICK
|
||||
* drain to the next prompt boundary is safe -- draining is best-effort
|
||||
* and simply resumes next beat. */
|
||||
if (g_mama_interpreting || g_idle_pump_active) {
|
||||
if (console_tx_count() == tx_before_idle) {
|
||||
console_cancel_deferred_line_start();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
g_idle_pump_active = 1;
|
||||
/* FABRIC-3.8.md Stage I (2026-09-24): the loop that used to drain every
|
||||
* other live VM's queue here, on Hera's stack (G3), is removed. Since
|
||||
* Stage E every born VM is a switch participant -- births are capped at
|
||||
@@ -1200,11 +1166,11 @@ static void sk_repl_idle(VM *active_vm)
|
||||
* here. Shown dead three ways before removal: by the condition it
|
||||
* required (a live non-participant), by that construction, and at run
|
||||
* time (a probe printed nothing through a boot, the Stage G interleave,
|
||||
* a full 8-worker DoE trial and the WIREBIND relay). What remains is
|
||||
* Hera's own queue: she is the one VM that is never switched to for her
|
||||
* mail, since she is the one running here. */
|
||||
(void)sk_hermes_drain_checkpoint(mama);
|
||||
g_idle_pump_active = 0;
|
||||
* a full 8-worker DoE trial and the WIREBIND relay). What remained was
|
||||
* Hera's own queue -- and FABRIC-3.9.8 Stage O-B (FABRIC-3.9.0.md §2.5)
|
||||
* moved that too, to sk_vm_idle_point(): as an observer acting on
|
||||
* events, her mail is acted on the moment she is idle, not at this
|
||||
* beat's ~1 s cadence. Nothing is drained here now. */
|
||||
|
||||
if (console_tx_count() == tx_before_idle) {
|
||||
console_cancel_deferred_line_start();
|
||||
@@ -1359,7 +1325,8 @@ int sk_console_getkey(VM *active_vm)
|
||||
VM *live_active = g_repl_active_vm ? g_repl_active_vm : (VM *)sk_get_mama_vm();
|
||||
sk_repl_idle(live_active);
|
||||
}
|
||||
sk_vm_idle_point(); /* FABRIC-3.8.md §3.13.5 -- acts only between lines */
|
||||
sk_vm_idle_point(0); /* FABRIC-3.8.md §3.13.5 -- acts only between lines; a key
|
||||
* read is someone mid-interaction: no self-drain (O-B) */
|
||||
arch_relax();
|
||||
}
|
||||
}
|
||||
@@ -1383,7 +1350,8 @@ void sk_repl_headless_wait(VM *mama)
|
||||
g_last_beat_tick = now;
|
||||
sk_repl_idle(mama);
|
||||
}
|
||||
sk_vm_idle_point(); /* FABRIC-3.8.md §3.13.5 */
|
||||
sk_vm_idle_point(1); /* FABRIC-3.8.md §3.13.5; no line is being typed: Hera's mail
|
||||
* is drained here too (O-B) */
|
||||
arch_relax();
|
||||
}
|
||||
}
|
||||
@@ -1585,8 +1553,11 @@ int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt)
|
||||
*/
|
||||
/* FABRIC-3.8.md §3.13.5 (Stage C-b): Hera waiting for a line is
|
||||
* the idle point -- the coolest VM with work runs now, not at
|
||||
* the next SK_IDLE_BEAT_INTERVAL (that cadence is the timer's). */
|
||||
sk_vm_idle_point();
|
||||
* the next SK_IDLE_BEAT_INTERVAL (that cadence is the timer's).
|
||||
* FABRIC-3.9.8 Stage O-B: so is Hera's own mail -- but only at
|
||||
* n == 0, so a drained payload never prints into a line being
|
||||
* typed (the gate the old idle-beat drain had). */
|
||||
sk_vm_idle_point(n == 0);
|
||||
arch_relax(); /* PAUSE — reduce power, maintain tight poll */
|
||||
continue;
|
||||
}
|
||||
@@ -1735,16 +1706,11 @@ static char g_kh_console_cmd_buf[SK_HERMES_CHUNK_MAX_PAYLOAD];
|
||||
|
||||
static void sk_repl_dispatch_line(VM *vm, const char *input)
|
||||
{
|
||||
/* H1 reentrancy guard: while this dispatched line executes on Hera
|
||||
* herself, sk_repl_idle() must defer its MSG-TICK pump -- calling
|
||||
* vm_interpret(mama, ...) from inside a mid-line KEY/EXPECT would
|
||||
* re-enter mama's interpreter and clobber its in-flight input buffer
|
||||
* (see the guard's comment at sk_repl_idle()). A child VM's console
|
||||
* turn leaves mama idle, so the pump stays safe there and the guard is
|
||||
* only latched for Hera. */
|
||||
int on_mama = (vm == (VM *)sk_get_mama_vm());
|
||||
int saved = g_mama_interpreting;
|
||||
if (on_mama) g_mama_interpreting = 1;
|
||||
/* (The H1 reentrancy flag, g_mama_interpreting, is gone -- FABRIC-3.9.8
|
||||
* Stage O-B. It guarded sk_repl_idle()'s timer-paced drain of Hera's
|
||||
* mail; that drain moved to sk_vm_idle_point(), whose own
|
||||
* frames.count != 0 check already refuses while this line, or any
|
||||
* word or drain inside it, is running on Hera.) */
|
||||
|
||||
if (sk_repl_line_calls_use(input)) {
|
||||
vm_interpret(vm, input);
|
||||
@@ -1807,7 +1773,7 @@ static void sk_repl_dispatch_line(VM *vm, const char *input)
|
||||
vm_interpret(vm, input);
|
||||
|
||||
out:
|
||||
g_mama_interpreting = saved;
|
||||
return;
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
|
||||
@@ -218,13 +218,24 @@ void sk_vm_switch_checkpoint(VM *vm) {
|
||||
* frame (nothing in progress), so she becomes eligible again only through
|
||||
* mail or pending console input, or is returned to by a trampoline with
|
||||
* nothing left to do. */
|
||||
void sk_vm_idle_point(void) {
|
||||
void sk_vm_idle_point(int may_drain_self) {
|
||||
VM *self = g_switch_current_vm;
|
||||
VM *next;
|
||||
|
||||
if (!self || self != (VM *)sk_get_mama_vm()) return;
|
||||
if (self->frames.count != 0) return; /* inside a word (KEY/EXPECT): not idle */
|
||||
|
||||
/* FABRIC-3.9.8 Stage O-B (FABRIC-3.9.0.md §2.5): Hera acts on her own
|
||||
* mail the moment she is idle -- one message, then back to the caller's
|
||||
* poll loop, which calls again. Was sk_repl_idle()'s drain, paced by the
|
||||
* ~1 s SK_IDLE_BEAT_INTERVAL. Safe here by the check just above: no
|
||||
* frame means no line, word or drain of hers is in progress, so the
|
||||
* drain can't re-enter her interpreter. */
|
||||
if (may_drain_self && sk_hermes_pending_count(self->stadium_vm_id) > 0) {
|
||||
(void)sk_hermes_drain_checkpoint(self);
|
||||
return;
|
||||
}
|
||||
|
||||
sk_vm_switch_reap_pending();
|
||||
next = sk_vm_switch_decide(self, 0);
|
||||
if (!next) return;
|
||||
|
||||
Reference in New Issue
Block a user