Stage K-4: quota slots released to the best donor; message cells out of the admission fallback (FABRIC-3.10.8 §4.4)

- stadium_release_quota(): evict every patron but pending messages, mark
  the slot retiring; stadium_retire_sweep() releases a retiring slot once
  nothing is resident -- free cells to the best donor (ruled), word row
  and block-table entries cleared, slot free for reuse. Swept at release
  and after evictions while any slot is retiring.
- Called on KILL and reap, and for the nine synthetic self-test ids after
  the boot self-tests. Hera's slot never released.
- Admission fallback skips DELIVER (message) cells.
- Probe (removed): slots in use == live VMs after boot and after 56-64
  churned VMs on all three ISAs; forced fallback kept the message cell
  resident and intact; conserved everywhere; K-1's post-churn Hermes
  refusal gone (1 2 + . -> 3 on the churned system). Heap growth per
  28-VM churn halved (~3.4 KB/VM still retained -- open as K-8).
- Standard sequence on amd64/aarch64/riscv64: hash, G, ledger unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-26 05:44:33 -04:00
co-authored by Claude Opus 5.5
parent 9e41fb03ab
commit 1166713cc9
21 changed files with 56959 additions and 9 deletions
+7
View File
@@ -450,6 +450,10 @@ void capsule_vm_force_reap(VMUuid vm_id) {
capsule_vm_hermes_depart(vm_id, entry->name, was_observer);
}
/* FABRIC-3.10.8 K-4: its Stadium quota slot is released (after the
* Hermes departure above, which returns its pending mail). */
(void)stadium_release_quota(vm_id);
capsule_parity_log_kill(vm_id, entry->name);
console_puts("REAP: ");
@@ -635,6 +639,9 @@ int capsule_vm_kill(const char *name) {
capsule_vm_hermes_depart(vm_id, entry->name, was_observer);
}
/* FABRIC-3.10.8 K-4: its Stadium quota slot is released. */
(void)stadium_release_quota(vm_id);
sk_spin_lock(&g_lock_registry);
entry->vm_ptr = (void *)0;
entry->state = VM_STATE_DEAD;
+17
View File
@@ -1846,6 +1846,23 @@ static void kernel_main_deep(BootInfo *boot_info) {
}
}
/* FABRIC-3.10.8 K-4: the self-tests above granted Stadium quotas to
* synthetic ids (hi 0; lo 1, 3, 7, 11, 13, 15, 16, 19, 20). They are
* released here, now that quota slots can be: before K-4 each kept its
* slot and cells for the whole boot. An id whose grant was refused has
* no slot, and its release is a no-op. */
{
static const uint64_t synthetic_lo[] = { 1, 3, 7, 11, 13, 15, 16, 19, 20 };
size_t k, released = 0;
for (k = 0; k < sizeof synthetic_lo / sizeof synthetic_lo[0]; k++) {
VMUuid id;
id.hi = 0;
id.lo = synthetic_lo[k];
if (stadium_release_quota(id) == 0) released++;
}
print_uint("Stadium: self-test quotas released=", (uint64_t)released);
}
/* Decided 2026-09-05: no console for the running system unless a
* thumbdrive is present -- headless by default (EMERGENCY_CONSOLE_
* ENABLED off), reusing that flag's own existing "does this build
+168 -9
View File
@@ -44,6 +44,9 @@
#include "block_subsystem.h" /* blk_flush() -- STADIUM_BEHAVIOUR_MIGRATE's real write-back action */
#include "starkernel/session.h" /* session_register()/session_set_pinned() -- FABRIC-2.md §H.12 step 4 */
#include "starkernel/spinlock.h" /* FABRIC-3.10.8 MC-0d -- per-slot locks, g_lock_stadium */
#include "starkernel/vm/stadium_words.h" /* K-4 -- stadium_words_slot_released() */
#include "starkernel/vm/stadium_blocks.h" /* K-4 -- stadium_blocks_slot_released() */
#include "starkernel/vm_uuid.h" /* K-4 -- vm_uuid_is_hera() */
static StadiumCell *stadium_cell_array = (StadiumCell *)0;
static uint8_t *stadium_bitmap = (uint8_t *)0;
@@ -113,6 +116,12 @@ typedef struct {
* have decayed away (recorded by kernel-Hermes,
* FABRIC-3.6.md task 2.7). Full invariant:
* Σ(resident) + reservoir + consumed == Q48_ONE. */
int retiring; /* FABRIC-3.10.8 K-4: its VM is gone (stadium_release_
* quota()); every patron but its pending messages has
* been evicted, and the slot is released -- cells to
* the best donor, slot free for reuse -- once the last
* of those messages leaves (stadium_retire_sweep()).
* Never a donor meanwhile. */
volatile uint32_t lock; /* FABRIC-3.10.8 MC-0d: this slot's own lock (a
* spinlock.h lock word). Covers every field above,
* and this slot's cells: their headers, owner and
@@ -312,6 +321,7 @@ int stadium_boot_init(void) {
quotas[i].reservoir = 0;
quotas[i].consumed = 0;
quotas[i].lock = 0;
quotas[i].retiring = 0;
}
}
quotas[0].vm_id = vm_uuid_hera();
@@ -558,6 +568,9 @@ int stadium_evict_credit(size_t cell_index, uint64_t *credited) {
if (slot < 0) return -1;
rc = evict_locked(cell_index, credited);
slot_unlock(slot);
/* K-4: this may have been a retiring slot's last message. Cheap when
* nothing is retiring (one atomic load). */
stadium_retire_sweep();
return rc;
}
@@ -645,6 +658,10 @@ size_t stadium_admit_in_slot(int slot, const StadiumPatronHeader *candidate) {
if (h->flags & STADIUM_FLAG_PIN) continue;
if (h->contains != STADIUM_CONTAINS_NONE) continue;
/* FABRIC-3.10.8 K-4: a message (DELIVER) cell leaves only through
* its release (kernel-Hermes). Evicted here, its message would keep
* pointing at a cell another patron then takes. */
if (h->behaviour == (uint8_t)STADIUM_BEHAVIOUR_DELIVER) continue;
{
uint64_t d = stadium_density(idx);
@@ -740,6 +757,7 @@ int stadium_grant_quota(VMUuid new_vm_id, VMUuid from_vm_id) {
stadium_quotas[new_slot].reservoir = Q48_ONE;
stadium_quotas[new_slot].consumed = 0;
stadium_quotas[new_slot].lock = 0;
stadium_quotas[new_slot].retiring = 0;
__atomic_store_n(&stadium_quotas[new_slot].in_use, 1, __ATOMIC_RELEASE);
sk_spin_unlock(&g_lock_stadium);
@@ -762,25 +780,166 @@ int stadium_grant_quota(VMUuid new_vm_id, VMUuid from_vm_id) {
* holds a quota yet (Stadium not initialized, or called before
* Hera's own boot-time grant).
*/
VMUuid stadium_best_donor(void) {
/* The in-use, non-retiring slot with the most free cells, other than
* `exclude` (-1 for none). -1 if there is none. K-4: a retiring slot is
* never a donor -- its cells are on their way out. */
static int best_donor_slot(int exclude) {
size_t i;
size_t best_count = 0;
int found = 0;
VMUuid best = vm_uuid_none();
if (!stadium_initialized) return best;
int best = -1;
for (i = 0; i < stadium_max_vm_count_val; i++) {
if (!stadium_quotas[i].in_use) continue;
if (!found || stadium_quotas[i].free_count > best_count) {
if (!stadium_quotas[i].in_use || stadium_quotas[i].retiring) continue;
if ((int)i == exclude) continue;
if (best < 0 || stadium_quotas[i].free_count > best_count) {
best_count = stadium_quotas[i].free_count;
best = stadium_quotas[i].vm_id;
found = 1;
best = (int)i;
}
}
return best;
}
VMUuid stadium_best_donor(void) {
int slot;
if (!stadium_initialized) return vm_uuid_none();
slot = best_donor_slot(-1);
return (slot < 0) ? vm_uuid_none() : stadium_quotas[slot].vm_id;
}
/*
* FABRIC-3.10.8 K-4: quota slots are released (ruled 2026-09-26: the cells
* go to the best donor). They never were -- a dead VM kept its slot and
* every cell in it, and each birth halved the best donor, so churn starved
* later births (a console VM born after 56 churned workers couldn't admit a
* message cell).
*
* Two steps, because a dead VM's slot can still hold cells for messages it
* SENT that sit in other VMs' queues: evicting those would leave kernel-
* Hermes pointing at cells another patron then takes.
* 1. stadium_release_quota(): every other patron is evicted (its heat back
* to the slot's own reservoir), and the slot is marked retiring.
* 2. stadium_retire_sweep(): a retiring slot with nothing left resident is
* released -- its free cells join the best donor's free list, its word
* row and block-table entries are cleared, and the slot is free for the
* next grant. Swept at release, and after any eviction while a slot is
* retiring (the release of its last message).
* The dead VM's conserved share (Q48_ONE) leaves with the slot; the donor
* gains free cells only, with no heat, so its own sum is unchanged.
*/
static uint32_t g_retiring_slots; /* atomic: slots waiting for step 2 */
static uint64_t g_slots_released; /* under g_lock_stadium */
uint64_t stadium_slots_released(void) { return g_slots_released; }
/* Step 1's eviction, `slot` locked. Returns the patrons left resident:
* pending messages, and anything eviction refuses (a patron holding
* another) -- the latter logged. A dead VM holds no pins that matter, so
* pins are cleared first. */
static size_t retire_evict_locked(int slot) {
size_t idx = stadium_quotas[slot].resident_head, left = 0;
while (idx != STADIUM_CELL_NONE) {
size_t next = link_to_size(stadium_resident_next[idx]);
StadiumPatronHeader *h = &stadium_cell_array[idx].header;
if (h->behaviour == (uint8_t)STADIUM_BEHAVIOUR_DELIVER) {
left++;
} else {
h->flags &= (uint8_t)~STADIUM_FLAG_PIN;
if (evict_locked(idx, (uint64_t *)0) != 0) {
left++;
log_message(LOG_ERROR, "Stadium: retiring slot %d: cell %zu can't be evicted (contains %u)",
slot, idx, (unsigned)h->contains);
}
}
idx = next;
}
return left;
}
void stadium_retire_sweep(void) {
size_t i;
if (!stadium_initialized || __atomic_load_n(&g_retiring_slots, __ATOMIC_ACQUIRE) == 0) return;
sk_spin_lock(&g_lock_stadium);
for (i = 0; i < stadium_max_vm_count_val; i++) {
int slot = (int)i, donor;
size_t head, count, idx, tail;
if (!stadium_quotas[slot].in_use || !stadium_quotas[slot].retiring) continue;
/* Detach its free list, if nothing is resident any more. */
slot_lock(slot);
if (stadium_quotas[slot].resident_head != STADIUM_CELL_NONE) {
slot_unlock(slot);
continue;
}
head = stadium_quotas[slot].free_head;
count = stadium_quotas[slot].free_count;
stadium_quotas[slot].free_head = STADIUM_CELL_NONE;
stadium_quotas[slot].free_count = 0;
stadium_words_slot_released(slot); /* its word row: slot lock held */
slot_unlock(slot);
/* The cells join the best donor (one slot lock at a time). */
donor = best_donor_slot(slot);
if (donor >= 0 && head != STADIUM_CELL_NONE) {
slot_lock(donor);
tail = STADIUM_CELL_NONE;
for (idx = head; idx != STADIUM_CELL_NONE; idx = link_to_size(stadium_cell_array[idx].header.link)) {
stadium_owner[idx] = (uint16_t)donor;
tail = idx;
}
stadium_cell_array[tail].header.link = size_to_link(stadium_quotas[donor].free_head);
stadium_quotas[donor].free_head = head;
stadium_quotas[donor].free_count += count;
slot_unlock(donor);
} else if (head != STADIUM_CELL_NONE) {
/* No other live slot (can't happen while Hera lives): keep them. */
slot_lock(slot);
stadium_quotas[slot].free_head = head;
stadium_quotas[slot].free_count = count;
slot_unlock(slot);
continue;
}
stadium_blocks_slot_released(slot); /* its block-table entries: global lock held */
stadium_quotas[slot].vm_id = vm_uuid_none();
stadium_quotas[slot].resident_head = STADIUM_CELL_NONE;
stadium_quotas[slot].reservoir = 0;
stadium_quotas[slot].consumed = 0;
stadium_quotas[slot].retiring = 0;
__atomic_store_n(&stadium_quotas[slot].in_use, 0, __ATOMIC_RELEASE);
__atomic_sub_fetch(&g_retiring_slots, 1, __ATOMIC_RELEASE);
g_slots_released++;
}
sk_spin_unlock(&g_lock_stadium);
}
int stadium_release_quota(VMUuid vm_id) {
int slot;
size_t left;
if (!stadium_initialized) return -1;
if (vm_uuid_is_hera(vm_id)) return -1; /* the root is never released */
sk_spin_lock(&g_lock_stadium);
slot = quota_slot_for_vm(vm_id);
if (slot < 0 || stadium_quotas[slot].retiring) {
sk_spin_unlock(&g_lock_stadium);
return -1;
}
slot_lock(slot);
left = retire_evict_locked(slot);
stadium_quotas[slot].retiring = 1;
slot_unlock(slot);
__atomic_add_fetch(&g_retiring_slots, 1, __ATOMIC_RELEASE);
sk_spin_unlock(&g_lock_stadium);
(void)left;
stadium_retire_sweep();
return 0;
}
/* Shared by stadium_cell_heat_get()/_set(): resident AND owned by vm_id's
* own quota slot. Returns the quota slot on success, -1 on any refusal. */
static int owned_resident_slot(VMUuid vm_id, size_t cell_index) {
+14
View File
@@ -303,6 +303,20 @@ static void block_dispatch_locked(int slot, uint32_t lbn, uint64_t heartbeat_tic
}
}
void stadium_blocks_slot_released(int slot) {
size_t i;
if (!blocks_initialized || slot < 0) return;
for (i = 0; i < block_slots_cap; i++) {
if (block_slots[i].state == BLOCK_SLOT_OCCUPIED && block_slots[i].quota_slot == (int16_t)slot) {
block_slots[i].state = BLOCK_SLOT_TOMBSTONE;
}
}
if ((size_t)slot < stadium_max_vm_count()) {
stat_promotions[slot] = 0;
stat_evictions[slot] = 0;
}
}
/* Freestanding: no libc printf. Same small utility stadium_words.c
* duplicates locally (and stadium.c before it) -- prints an unsigned
* decimal, no leading zeros. */
+9
View File
@@ -331,6 +331,15 @@ void stadium_word_forget(VMUuid vm_id, uint32_t word_id) {
stadium_slot_unlock(slot);
}
void stadium_words_slot_released(int slot) {
if (!words_initialized || slot < 0 || (size_t)slot >= stadium_max_vm_count()) return;
if (word_slots[slot]) kfree(word_slots[slot]);
word_slots[slot] = (StadiumWordSlot *)0;
row_cap[slot] = 0;
stat_promotions[slot] = 0;
stat_evictions[slot] = 0;
}
void stadium_words_stats(VMUuid vm_id, uint64_t *promotions, uint64_t *evictions) {
int slot = stadium_slot_lock_vm(vm_id);