A dead VM leaves kernel-Hermes: KILL/reap drop it from every channel and release its pending mail (found starting M-B)
capsule_vm_kill() and capsule_vm_force_reap() never touched kernel-Hermes: a dead VM stayed a member of channel 0 and every channel it joined, its undelivered mail was never released, and anything published to it afterwards held heat forever. M-B's autonomous events would leak on every dead subscriber. Captain Bob: "Fix first, own commit." - sk_hermes_vm_departed(): unsubscribe from every channel, release + pop the pending queue (heat back to each funder, ledger exact), free the queue entry. Called from both kill and reap, with a HERMES departure line. Console evidence identical on amd64/aarch64/riscv64: wa with 2 pending -> "left 2 channel(s), released 2 pending", gone from .CHANNELS, PUBLISH to its channel delivers 0; acceptance ledger held=2048 (6144 without the release). Boot parity/dict_hash unchanged; acceptance passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
879d00dcf9
commit
b562e16ccf
@@ -563,3 +563,47 @@ Evidence is named **before** each stage runs.
|
|||||||
transcripts. It is pre-existing: the same message is in riscv64 logs back to 2026-09-10,
|
transcripts. It is pre-existing: the same message is in riscv64 logs back to 2026-09-10,
|
||||||
including this morning's S-1b follow-up acceptance (`-142946`). A TCG timing issue in the
|
including this morning's S-1b follow-up acceptance (`-142946`). A TCG timing issue in the
|
||||||
virtio-blk driver, not M-A.
|
virtio-blk driver, not M-A.
|
||||||
|
- [x] **Fix, ahead of M-B: a dead VM leaves kernel-Hermes** (found while starting M-B, 2026-09-24).
|
||||||
|
Captain Bob: *"Fix first, own commit."*
|
||||||
|
|
||||||
|
**The defect.** `capsule_vm_kill()` and `capsule_vm_force_reap()` tore a VM down but never
|
||||||
|
touched kernel-Hermes:
|
||||||
|
- The dead VM stayed a member of every channel it had joined, channel 0 included. It showed in
|
||||||
|
`.CHANNELS` as a nameless `?…` member.
|
||||||
|
- Its undelivered mail was never released.
|
||||||
|
- Anything published to it afterwards was queued to a VM that would never drain it, its heat
|
||||||
|
held forever.
|
||||||
|
|
||||||
|
M-B makes channel delivery automatic, so every event reaching a dead subscriber would have
|
||||||
|
leaked.
|
||||||
|
|
||||||
|
**The change:**
|
||||||
|
- `sk_hermes_vm_departed(vm_id, …)` (`kernel_hermes.c`) drops the VM from every channel and
|
||||||
|
releases and pops its pending queue. Each message's heat goes back to whoever funded it, and
|
||||||
|
the ledger stays exact. The queue-table entry is freed for reuse.
|
||||||
|
- `capsule_birth.c` calls it from both `capsule_vm_kill()` and `capsule_vm_force_reap()`, and
|
||||||
|
reports `HERMES: <name> departed -- left N channel(s), released M pending`.
|
||||||
|
- `capsule_vm_kill_all_nonmama()` is left alone: its only callers (BYE, cold restart) reset the
|
||||||
|
machine immediately afterwards.
|
||||||
|
|
||||||
|
**Evidence** (console; the same transcript on all three ISAs; `logs/20260924-192807/amd64/`,
|
||||||
|
`-194215/aarch64/`, `-195620/riscv64/`):
|
||||||
|
- `CD S" wa" JOIN-VM .` then `.CHANNELS` shows `wa` in channel 0 and `CD`.
|
||||||
|
- With 2 `SEND`s pending to `wa` (`MAIL?` gives 2, and Hera's `STADIUM-RES@` fell by exactly
|
||||||
|
4096 = 2 × Q_SLOT), `S" wa" KILL` gives `HERMES: wa departed -- left 2 channel(s), released
|
||||||
|
2 pending`.
|
||||||
|
- `.CHANNELS` then shows `ch 0 (4 members): Hera Hestia Artemis wb` and `CD` with 0 members.
|
||||||
|
- `S" 1 DROP" CD PUBLISH .` gives `0`: nothing queued to the dead VM.
|
||||||
|
- `wb`, killed with no mail, gives `left 1 channel(s), released 0 pending`.
|
||||||
|
- **The exact check is the ledger.** On the same boots, acceptance's relay shows `held=2048`,
|
||||||
|
the one in-flight console message. Without the release, `wa`'s two stranded messages would
|
||||||
|
have left it at 6144.
|
||||||
|
- Hera's reservoir is not a clean meter for this: it sits at the word-dispatch floor (Q48_ONE /
|
||||||
|
3 = 21845), and Stadium word-cooling pushes heat back into it between lines.
|
||||||
|
|
||||||
|
**Not exercised:** the `capsule_vm_force_reap()` call. No reap happened in these runs. It
|
||||||
|
shares the same helper.
|
||||||
|
|
||||||
|
**Acceptance** (same boots): boot parity and the `dict_hash` set are unchanged from M-A, and
|
||||||
|
the O-A checks pass (40/40/48, `HHH`, `3` mid-stream). The relay answers `3`, idle log growth
|
||||||
|
is 0, there are no faults, and QEMU exits cleanly.
|
||||||
|
|||||||
@@ -628,6 +628,14 @@ typedef struct VM VM;
|
|||||||
*/
|
*/
|
||||||
int sk_hermes_drain_checkpoint(VM *vm);
|
int sk_hermes_drain_checkpoint(VM *vm);
|
||||||
|
|
||||||
|
/* sk_hermes_vm_departed - a VM has died (KILL or reap): drop it from every
|
||||||
|
* channel (channel 0 included) and release its undelivered mail, each
|
||||||
|
* message's heat returning to whoever funded it, ledger exact. Before this
|
||||||
|
* (found at FABRIC-3.9.8 Stage M-B), a dead VM stayed a member everywhere
|
||||||
|
* and anything published to it afterwards sat in its queue forever with
|
||||||
|
* its heat held. Counts are returned for the caller's report. Always 0. */
|
||||||
|
int sk_hermes_vm_departed(VMUuid vm_id, int *channels_left, int *released);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Payload bound and chunking (FABRIC-3.6.md task 3.5, B4 / FABRIC-3.5.md
|
* Payload bound and chunking (FABRIC-3.6.md task 3.5, B4 / FABRIC-3.5.md
|
||||||
* SXLV.3; chunk framing ruled 2026-09-21: each chunk carries (msg_id,
|
* SXLV.3; chunk framing ruled 2026-09-21: each chunk carries (msg_id,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,7 @@
|
|||||||
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.md §XXVIII Stage 4 -- capsule_vm_force_reap() */
|
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.md §XXVIII Stage 4 -- capsule_vm_force_reap() */
|
||||||
#include "starkernel/vm/switch.h" /* FABRIC-3.8.md §3.5 -- sk_vm_parked_idle(), current VM */
|
#include "starkernel/vm/switch.h" /* FABRIC-3.8.md §3.5 -- sk_vm_parked_idle(), current VM */
|
||||||
#include "starkernel/vm/kernel_hermes.h" /* FABRIC-3.6.md task 3.2 -- sk_hermes_channel_subscribe() */
|
#include "starkernel/vm/kernel_hermes.h" /* FABRIC-3.6.md task 3.2 -- sk_hermes_channel_subscribe() */
|
||||||
|
#include "freestanding/stdio.h" /* snprintf() -- the HERMES departure report */
|
||||||
#include "word_source/include/framebuffer_words.h" /* FABRIC-3.6.md task 1.8 -- register_framebuffer_words() */
|
#include "word_source/include/framebuffer_words.h" /* FABRIC-3.6.md task 1.8 -- register_framebuffer_words() */
|
||||||
#include "vm.h"
|
#include "vm.h"
|
||||||
#include "platform_alloc.h"
|
#include "platform_alloc.h"
|
||||||
@@ -340,6 +341,19 @@ void capsule_vm_set_pending_reap(VMUuid vm_id, int pending) {
|
|||||||
if (entry) entry->pending_reap = pending;
|
if (entry) entry->pending_reap = pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A dead VM leaves kernel-Hermes: every channel, and its undelivered mail
|
||||||
|
* released (heat back to each sender). Found at FABRIC-3.9.8 Stage M-B --
|
||||||
|
* before this, KILL/reap left it a member everywhere, and anything
|
||||||
|
* published to it afterwards held heat forever. */
|
||||||
|
static void capsule_vm_hermes_depart(VMUuid vm_id, const char *name) {
|
||||||
|
int left = 0, released = 0;
|
||||||
|
char line[96];
|
||||||
|
sk_hermes_vm_departed(vm_id, &left, &released);
|
||||||
|
snprintf(line, sizeof line, "HERMES: %s departed -- left %d channel(s), released %d pending",
|
||||||
|
(name && name[0]) ? name : "(unnamed)", left, released);
|
||||||
|
console_println(line);
|
||||||
|
}
|
||||||
|
|
||||||
void capsule_vm_force_reap(VMUuid vm_id) {
|
void capsule_vm_force_reap(VMUuid vm_id) {
|
||||||
VMRegistryEntry *entry = vm_find_entry_ptr(vm_id);
|
VMRegistryEntry *entry = vm_find_entry_ptr(vm_id);
|
||||||
if (!entry) return;
|
if (!entry) return;
|
||||||
@@ -378,6 +392,7 @@ void capsule_vm_force_reap(VMUuid vm_id) {
|
|||||||
* pending_reap -- a reaped VM is no longer a switch-signal
|
* pending_reap -- a reaped VM is no longer a switch-signal
|
||||||
* participant either way. */
|
* participant either way. */
|
||||||
sk_vm_switch_signal_unregister(vm_id);
|
sk_vm_switch_signal_unregister(vm_id);
|
||||||
|
capsule_vm_hermes_depart(vm_id, entry->name);
|
||||||
|
|
||||||
capsule_parity_log_kill(vm_id, entry->name);
|
capsule_parity_log_kill(vm_id, entry->name);
|
||||||
|
|
||||||
@@ -528,6 +543,7 @@ int capsule_vm_kill(const char *name) {
|
|||||||
* an ordinary KILL left its slot behind (G6), to be walked by every
|
* an ordinary KILL left its slot behind (G6), to be walked by every
|
||||||
* decision and never freed. No-op for a VM that never registered. */
|
* decision and never freed. No-op for a VM that never registered. */
|
||||||
sk_vm_switch_signal_unregister(vm_id);
|
sk_vm_switch_signal_unregister(vm_id);
|
||||||
|
capsule_vm_hermes_depart(vm_id, name);
|
||||||
|
|
||||||
entry->vm_ptr = (void *)0;
|
entry->vm_ptr = (void *)0;
|
||||||
entry->state = VM_STATE_DEAD;
|
entry->state = VM_STATE_DEAD;
|
||||||
|
|||||||
@@ -704,6 +704,33 @@ int sk_hermes_pending_pop(VMUuid vm_id) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int sk_hermes_vm_departed(VMUuid vm_id, int *channels_left, int *released) {
|
||||||
|
SkHermesPendingQueue *q;
|
||||||
|
SkHermesMessage *m;
|
||||||
|
int ch, left = 0, rel = 0;
|
||||||
|
|
||||||
|
/* Every channel it belonged to, channel 0 included. */
|
||||||
|
for (ch = 0; ch < sk_hermes_channel_capacity_val; ch++) {
|
||||||
|
if (channel_valid(ch) && sk_hermes_channel_unsubscribe(ch, vm_id) == 0) left++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Its undelivered mail: each message released (heat back to whoever
|
||||||
|
* funded it, ledger exact) and popped. The queue-table entry is then
|
||||||
|
* freed for reuse, keeping its grown ring (find_or_create_queue()). */
|
||||||
|
q = find_queue(vm_id);
|
||||||
|
if (q) {
|
||||||
|
while ((m = sk_hermes_pending_peek(vm_id)) != (SkHermesMessage *)0) {
|
||||||
|
if (sk_hermes_release(m) == 0) rel++;
|
||||||
|
sk_hermes_pending_pop(vm_id);
|
||||||
|
}
|
||||||
|
q->in_use = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channels_left) *channels_left = left;
|
||||||
|
if (released) *released = rel;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int sk_hermes_drain_checkpoint(VM *vm) {
|
int sk_hermes_drain_checkpoint(VM *vm) {
|
||||||
SkHermesMessage *msg;
|
SkHermesMessage *msg;
|
||||||
VMUuid self;
|
VMUuid self;
|
||||||
|
|||||||
Reference in New Issue
Block a user