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:
co-authored by
Claude Opus 5.5
parent
9e41fb03ab
commit
1166713cc9
@@ -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;
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user