Stage M-B: autonomous events -- EV-DONE / EV-BORN / EV-DIED published by the kernel itself (FABRIC-3.9.0 §7.3, M-3..M-6)
- Permanent event channels 1/2/3 created at boot beside channel 0, exposed as FORTH constants EV-DONE / EV-BORN / EV-DIED; CHANNEL-CLOSE refuses 0..3. - sk_hermes_event(): `S" <name>" ON-...` as type 32 to every member except the VM it's about; never blocks -- undeliverable copies are dropped, counted (EV-DROPPED), printed and logged. - Fired for participants only: EV-DONE from the switch trampoline when real work runs dry (funded by the VM), EV-BORN at a LIVE VM's first naming (funded by the newborn), EV-DIED on KILL/reap after departure (funded by the root). VM.ev_real_work keeps event handling from firing EV-DONE, so mutual subscribers can't storm. Console evidence identical on amd64/aarch64/riscv64: one BORN per birth, one DONE per job run dry (2 queued -> 1), one DIED per KILL, no SEND in any job text; storm guard: exactly one reaction; channels 1-3 unclosable; non-joined VMs see nothing; EV-DROPPED 0. Flood probe (reverted): 32 delivered, 7 dropped and counted, never blocked, all heat returned. dict_hash set changes by the 4 words (546), identical across ISAs; acceptance passes with held=2048. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b562e16ccf
commit
fe55038e9a
@@ -345,13 +345,21 @@ void capsule_vm_set_pending_reap(VMUuid vm_id, int pending) {
|
||||
* 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) {
|
||||
static void capsule_vm_hermes_depart(VMUuid vm_id, const char *name, int was_observer) {
|
||||
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);
|
||||
|
||||
/* FABRIC-3.9.8 Stage M-B (§7.3): EV-DIED, after the departure above so
|
||||
* the dead VM is already off every channel. Funded by the root -- a
|
||||
* dead VM can't pay (M-5); participants only (M-4). was_observer is
|
||||
* read by the caller before sk_vm_switch_signal_unregister() erases it. */
|
||||
if (!was_observer && name && name[0]) {
|
||||
(void)sk_hermes_event(SK_HERMES_CHANNEL_EV_DIED, vm_id, vm_uuid_hera(), name);
|
||||
}
|
||||
}
|
||||
|
||||
void capsule_vm_force_reap(VMUuid vm_id) {
|
||||
@@ -391,8 +399,11 @@ void capsule_vm_force_reap(VMUuid vm_id) {
|
||||
/* Generic cleanup, independent of whatever subsystem set
|
||||
* pending_reap -- a reaped VM is no longer a switch-signal
|
||||
* participant either way. */
|
||||
sk_vm_switch_signal_unregister(vm_id);
|
||||
capsule_vm_hermes_depart(vm_id, entry->name);
|
||||
{
|
||||
int was_observer = sk_vm_switch_signal_is_observer(vm_id);
|
||||
sk_vm_switch_signal_unregister(vm_id);
|
||||
capsule_vm_hermes_depart(vm_id, entry->name, was_observer);
|
||||
}
|
||||
|
||||
capsule_parity_log_kill(vm_id, entry->name);
|
||||
|
||||
@@ -408,10 +419,23 @@ void capsule_vm_force_reap(VMUuid vm_id) {
|
||||
|
||||
void capsule_vm_registry_set_name(VMUuid vm_id, const char *name) {
|
||||
VMRegistryEntry *entry;
|
||||
int first_naming;
|
||||
if (!name) return;
|
||||
entry = vm_find_entry_ptr(vm_id);
|
||||
if (!entry) return;
|
||||
first_naming = (entry->name[0] == '\0');
|
||||
vm_name_copy(entry->name, name);
|
||||
|
||||
/* FABRIC-3.9.8 Stage M-B (§7.3): EV-BORN. capsule_birth_baby() leaves
|
||||
* a VM unnamed; every birth path (BIRTH, WORKER-BIRTH, identity births,
|
||||
* console, runcap) names it here right after it reaches LIVE -- so a
|
||||
* first naming of a LIVE VM is the moment it's born, under the name
|
||||
* its subscribers know it by. Funded by the newborn (M-5), whose quota
|
||||
* is granted during birth; participants only (M-4). */
|
||||
if (first_naming && entry->state == VM_STATE_LIVE &&
|
||||
!sk_vm_switch_signal_is_observer(vm_id)) {
|
||||
(void)sk_hermes_event(SK_HERMES_CHANNEL_EV_BORN, vm_id, vm_id, entry->name);
|
||||
}
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
@@ -542,8 +566,11 @@ int capsule_vm_kill(const char *name) {
|
||||
* does -- a dead VM is no longer a switch participant. Before this,
|
||||
* 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. */
|
||||
sk_vm_switch_signal_unregister(vm_id);
|
||||
capsule_vm_hermes_depart(vm_id, name);
|
||||
{
|
||||
int was_observer = sk_vm_switch_signal_is_observer(vm_id);
|
||||
sk_vm_switch_signal_unregister(vm_id);
|
||||
capsule_vm_hermes_depart(vm_id, entry->name, was_observer);
|
||||
}
|
||||
|
||||
entry->vm_ptr = (void *)0;
|
||||
entry->state = VM_STATE_DEAD;
|
||||
|
||||
@@ -763,6 +763,14 @@ static void sk_word_dot_channels(VM *vm) {
|
||||
}
|
||||
}
|
||||
|
||||
/* FABRIC-3.9.8 Stage M-B (§7.3): the event channels' fixed ids, so JOIN
|
||||
* and the ACL can name them, and the M-6 counter the DoE can check. */
|
||||
static void sk_word_ev_done(VM *vm) { vm_push(vm, SK_HERMES_CHANNEL_EV_DONE); }
|
||||
static void sk_word_ev_born(VM *vm) { vm_push(vm, SK_HERMES_CHANNEL_EV_BORN); }
|
||||
static void sk_word_ev_died(VM *vm) { vm_push(vm, SK_HERMES_CHANNEL_EV_DIED); }
|
||||
/* EV-DROPPED ( -- n ) events that couldn't be admitted, system-wide */
|
||||
static void sk_word_ev_dropped(VM *vm) { vm_push(vm, (cell_t)sk_hermes_events_dropped()); }
|
||||
|
||||
void sk_repl_register_words(VM *vm) {
|
||||
register_word(vm, "BLK-ATTACH-ACK", sk_word_blk_attach_ack);
|
||||
register_word(vm, "KH-BLK-ATTACH-SEND", sk_word_kh_blk_attach_send);
|
||||
@@ -780,6 +788,11 @@ void sk_repl_register_words(VM *vm) {
|
||||
register_word(vm, "MAIL?", sk_word_mail_q);
|
||||
register_word(vm, ".MAIL", sk_word_dot_mail);
|
||||
register_word(vm, ".CHANNELS", sk_word_dot_channels);
|
||||
/* FABRIC-3.9.8 Stage M-B (§7.3) */
|
||||
register_word(vm, "EV-DONE", sk_word_ev_done);
|
||||
register_word(vm, "EV-BORN", sk_word_ev_born);
|
||||
register_word(vm, "EV-DIED", sk_word_ev_died);
|
||||
register_word(vm, "EV-DROPPED", sk_word_ev_dropped);
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include "log.h" /* Stage M-0: grow failures are logged at LOG_ERROR */
|
||||
#include "vm.h" /* task 3.4 -- VM struct fields, vm_interpret() */
|
||||
#include "starkernel/capsule_vm_switch_signal.h" /* FABRIC-3.8.md §3.13.3 -- wait stamps */
|
||||
#include "starkernel/capsule_run.h" /* VM_NAME_MAX -- Stage M-B event payloads */
|
||||
|
||||
/* Freestanding: no libc printf. Prints an unsigned decimal, no leading
|
||||
* zeros -- same small helper stadium.c/capsule_vm_switch_signal.c each
|
||||
@@ -353,6 +354,12 @@ int sk_hermes_channels_boot_init(void) {
|
||||
/* The common channel exists from boot (SXLV.1), empty -- VMs join it
|
||||
* at birth (kernel_main.c/capsule_birth.c task 3.2 wiring), not here. */
|
||||
table[SK_HERMES_CHANNEL_COMMON].in_use = 1;
|
||||
/* Stage M-B: the event channels, fixed ids beside channel 0. */
|
||||
if (max_vm_count > SK_HERMES_CHANNEL_PERMANENT_LAST) {
|
||||
table[SK_HERMES_CHANNEL_EV_DONE].in_use = 1;
|
||||
table[SK_HERMES_CHANNEL_EV_BORN].in_use = 1;
|
||||
table[SK_HERMES_CHANNEL_EV_DIED].in_use = 1;
|
||||
}
|
||||
|
||||
sk_hermes_channels = table;
|
||||
sk_hermes_channel_capacity_val = (int)max_vm_count;
|
||||
@@ -394,7 +401,10 @@ int sk_hermes_channel_create(void) {
|
||||
}
|
||||
|
||||
int sk_hermes_channel_destroy(int channel_id) {
|
||||
if (channel_id == SK_HERMES_CHANNEL_COMMON) return -1; /* permanent, SXLV.1 */
|
||||
/* Permanent: channel 0 (SXLV.1) and, since Stage M-B, the event
|
||||
* channels 1..3. */
|
||||
if (channel_id >= SK_HERMES_CHANNEL_COMMON &&
|
||||
channel_id <= SK_HERMES_CHANNEL_PERMANENT_LAST) return -1;
|
||||
if (!channel_valid(channel_id)) return -1;
|
||||
|
||||
/* Stage M-0: the member list is heap-grown -- free it, or create()'s
|
||||
@@ -678,6 +688,62 @@ int sk_hermes_publish(VMUuid from, int channel_id, uint32_t type,
|
||||
return delivered;
|
||||
}
|
||||
|
||||
/* Stage M-B (FABRIC-3.9.0.md §7.3): autonomous events. */
|
||||
static uint64_t sk_hermes_ev_delivered = 0;
|
||||
static uint64_t sk_hermes_ev_dropped = 0;
|
||||
|
||||
uint64_t sk_hermes_events_delivered(void) { return sk_hermes_ev_delivered; }
|
||||
uint64_t sk_hermes_events_dropped(void) { return sk_hermes_ev_dropped; }
|
||||
|
||||
int sk_hermes_event(int ev_channel, VMUuid about, VMUuid funder, const char *name) {
|
||||
static char payload[VM_NAME_MAX + 32];
|
||||
const char *handler;
|
||||
SkHermesMembership *m;
|
||||
size_t i;
|
||||
int len, delivered = 0, dropped = 0;
|
||||
|
||||
switch (ev_channel) {
|
||||
case SK_HERMES_CHANNEL_EV_DONE: handler = "ON-DONE"; break;
|
||||
case SK_HERMES_CHANNEL_EV_BORN: handler = "ON-BORN"; break;
|
||||
case SK_HERMES_CHANNEL_EV_DIED: handler = "ON-DIED"; break;
|
||||
default: return 0;
|
||||
}
|
||||
if (!channel_valid(ev_channel) || !name) return 0;
|
||||
|
||||
len = snprintf(payload, sizeof payload, "S\" %s\" %s", name, handler);
|
||||
if (len <= 0 || (size_t)len >= sizeof payload) return 0;
|
||||
|
||||
/* Copied by sk_hermes_send_one(), so one static payload serves every
|
||||
* member. The member list is read by index each time: a send never
|
||||
* changes channel membership. */
|
||||
m = &sk_hermes_channels[ev_channel].membership;
|
||||
for (i = 0; i < m->count; i++) {
|
||||
VMUuid to = m->members[i];
|
||||
if (vm_uuid_equal(to, about)) continue; /* never to the VM it's about */
|
||||
if (sk_hermes_send_one(funder, to, SK_HERMES_MSG_TYPE_EVENT, (uint32_t)ev_channel,
|
||||
payload, (uint32_t)(len + 1)) == 0) {
|
||||
delivered++;
|
||||
} else {
|
||||
dropped++;
|
||||
}
|
||||
}
|
||||
|
||||
sk_hermes_ev_delivered += (uint64_t)delivered;
|
||||
if (dropped > 0) {
|
||||
/* M-6: dropped, counted, logged -- never blocking. */
|
||||
sk_hermes_ev_dropped += (uint64_t)dropped;
|
||||
console_puts("Kernel-Hermes: event ");
|
||||
console_puts(handler);
|
||||
console_puts(" for ");
|
||||
console_puts(name);
|
||||
console_puts(" dropped for ");
|
||||
console_put_u64((uint64_t)dropped);
|
||||
console_println(" member(s)");
|
||||
log_message(LOG_ERROR, "Kernel-Hermes: %s event dropped x%d", handler, dropped);
|
||||
}
|
||||
return delivered;
|
||||
}
|
||||
|
||||
int sk_hermes_pending_count(VMUuid vm_id) {
|
||||
SkHermesPendingQueue *q = find_queue(vm_id);
|
||||
return q ? (int)q->count : 0;
|
||||
@@ -818,6 +884,9 @@ int sk_hermes_drain_checkpoint(VM *vm) {
|
||||
* ID when vm_interpret_kind() returns. */
|
||||
vm_interpret_kind(vm, (const char *)msg->payload_addr, VM_FRAME_DRAIN);
|
||||
drain_error = vm->error;
|
||||
/* Stage M-B: real (non-event) work arms EV-DONE for when the queue
|
||||
* runs dry (switch.c's trampoline); handling an event never does. */
|
||||
if (msg->type != SK_HERMES_MSG_TYPE_EVENT) vm->ev_real_work = 1;
|
||||
|
||||
memcpy(vm->input_buffer, saved_input, sizeof(saved_input));
|
||||
vm->input_length = saved_length;
|
||||
|
||||
@@ -156,6 +156,20 @@ void sk_vm_switch_entry(void) {
|
||||
if (drained == 0 && has_mail) {
|
||||
sk_hal_panic("switch trampoline: pending mail the drain did not consume");
|
||||
}
|
||||
/* FABRIC-3.9.8 Stage M-B (§7.3): a participant that did real work
|
||||
* and now has none tells whoever joined EV-DONE -- the OS emits its
|
||||
* completion; its own code never decides to. Funded by the VM it's
|
||||
* about (M-5); observers never announce their own idleness (M-4). */
|
||||
if (!has_mail && self->ev_real_work) {
|
||||
self->ev_real_work = 0;
|
||||
if (!sk_vm_switch_signal_is_observer(self->stadium_vm_id)) {
|
||||
VMRegistryEntry e;
|
||||
if (capsule_vm_registry_get(self->stadium_vm_id, &e) == 0) {
|
||||
(void)sk_hermes_event(SK_HERMES_CHANNEL_EV_DONE, self->stadium_vm_id,
|
||||
self->stadium_vm_id, e.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
sk_vm_switch_reap_pending();
|
||||
next = sk_vm_switch_decide(self, has_mail);
|
||||
if (!next) {
|
||||
|
||||
Reference in New Issue
Block a user