Fix pathological migration scan that stalled WIREBIND identity attach
Root cause (found by a fresh subagent after an extended live-debugging investigation into "identity 00 attaches slowly/stalls when Zuse never attached first this boot"): blk_migration_idle_check() was generalized earlier today to walk every attached device slot uniformly instead of hardcoding first_disk_slot() (Artemis's own disk). But its per-slot scan can only early-exit once it finds a devblock that is BOTH "hot" (claimed and worn) AND "free" -- and a just-attached, never-claimed USB identity drive can never satisfy the "hot" half by design (claiming only ever happens via blk_firsttouch_claim(), which only ever targets first_disk_slot()). So the scan ran to completion -- the drive's entire ~16,000 devblocks, mostly cache misses over slow emulated USB/BOT -- every single idle tick, forever, blocking sk_repl_idle() (and therefore the console and the storage-attach message round-trip) each time. Fix, in src/block_subsystem.c: a new has_ever_claimed flag on blk_dev_slot_t (set in blk_set_meta(), the single choke point every BLK_FLAG_CLAIMED transition passes through) skips the scan entirely, O(1), for any slot nothing has ever claimed -- the common case for a freshly-attached drive. A new migration_scan_lbn resume cursor bounds *any* slot's per-tick cost to MIGRATION_SCAN_BUDGET (256) devblocks examined, picking up where the previous tick left off instead of restarting from start_lbn every time -- restores this function's own documented "coarse cadence, cheap early-exit" design intent for every device, not just the one it used to hardcode. Also along the way (kept, all real improvements, verified live): - src/starkernel/usb/xhci.c: xhci_wait_bit()/xhci_bot_wait_for_idle() had zero yield hints in their MMIO-polling loops; added arch_relax() to both (matches virtio_blk.c below) -- a tight loop of nothing but MMIO reads can starve TCG's own host-side timer injection under QEMU. - src/starkernel/virtio/virtio_blk.c: vblk_io()'s spin bound was 33M iterations with zero logging on timeout; a single real (still not fully root-caused) timeout cost 31+ minutes of CPU before this was caught. Reduced to 1M and added a log line naming the failing sector, turning a silent, effectively-unbounded stall into a fast, loud failure -- callers already tolerate BLKIO_EIO. - src/starkernel/repl.c: blk_migration_idle_check() deferred for any idle tick where a storage-attach message round-trip is still pending, to keep the two block-subsystem-touching paths from interleaving; the existing MSG-TICK pump now checks the target VM's own dictionary for MSG-TICK (vm_find_word + acl_allow) before VM-EXECing into it, instead of spamming "VM-EXEC: ERROR" every tick for a VM that doesn't have -- or isn't allowed -- the word (e.g. a FORTH-79/83-locked-down identity); fixed a real -Wunused-variable build break in the EMERGENCY_CONSOLE_ ENABLED=1 path (unused today, but needed live to reproduce this bug with no identity attached at all). - src/starkernel/capsule/capsule_mint.c: dropped the dead S" common:messaging.4th" EXEC / MSG-CD-INIT lines from MINT_RESTRICTED_PERSONALITY -- a FORTH-79/83-locked-down identity has no legitimate use for a messaging vocabulary it can never call. Status: the pathological CPU-climbing scan is confirmed fixed (verified live: CPU stays flat across an extended run instead of climbing without bound). The WIREBIND storage-attach message round-trip still does not complete promptly in the "Zuse never attached, other identity attaches first" scenario -- a separate, still-open issue in the message-delivery path itself, not the scan. Tracked as follow-on work. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
63b8b3bc29
commit
1a263555e2
+93
-20
@@ -152,6 +152,17 @@ struct blk_dev_slot {
|
||||
/* BAM: one entry per user block, heap-allocated at attach time */
|
||||
blk_bam_entry_t *bam;
|
||||
|
||||
/* FABRIC-3.md, 2026-09-09: blk_migration_idle_check()'s own doc
|
||||
* comment on has_ever_claimed/migration_scan_lbn explains why these
|
||||
* exist -- see that function. */
|
||||
uint8_t has_ever_claimed; /* 1 once any devblock in this slot has
|
||||
* ever been BLK_FLAG_CLAIMED; wear-
|
||||
* leveling has nothing to do on a slot
|
||||
* that's still 0. */
|
||||
uint32_t migration_scan_lbn; /* resume cursor for the bounded per-
|
||||
* tick migration scan, absolute LBN,
|
||||
* wraps at start_lbn+user_blocks. */
|
||||
|
||||
blk_dev_slot_t *next;
|
||||
};
|
||||
|
||||
@@ -1301,6 +1312,10 @@ int blk_set_meta(uint32_t block_num, const blk_meta_t *meta) {
|
||||
if (!c) return BLK_EIO;
|
||||
c->meta[slot_pbn_pack_offset(rel_pbn)] = *meta;
|
||||
c->meta_dirty = 1;
|
||||
/* FABRIC-3.md, 2026-09-09: single choke point every BLK_FLAG_CLAIMED
|
||||
* transition passes through -- see blk_migration_idle_check()'s own
|
||||
* doc comment for why this flag exists. */
|
||||
if (meta->flags & BLK_FLAG_CLAIMED) slot->has_ever_claimed = 1;
|
||||
return BLK_OK;
|
||||
}
|
||||
|
||||
@@ -1567,39 +1582,97 @@ int blk_meta_relocate_devblock(uint32_t home_devblock, uint32_t target_devblock)
|
||||
* trigger-detection call site is the remaining gap. */
|
||||
#define MIGRATION_WEAR_THRESHOLD 10000u
|
||||
|
||||
void blk_migration_idle_check(void) {
|
||||
blk_dev_slot_t *slot = first_disk_slot();
|
||||
if (!slot) return;
|
||||
/* Devblocks actually read (blk_get_meta() calls, the expensive part --
|
||||
* real I/O on any cache miss) per slot, per idle tick. FABRIC-3.md,
|
||||
* 2026-09-09: blk_migration_idle_check_slot() used to scan a slot's
|
||||
* *entire* range in one synchronous call with no bound but "found both
|
||||
* halves of the exit condition" -- fine when this only ever ran against
|
||||
* Artemis's own fast, small, resident, already-claimed disk
|
||||
* (first_disk_slot()-only, pre-2026-09-09), but once generalized to walk
|
||||
* every attached slot (below), an unclaimed removable drive can never
|
||||
* satisfy the "hot" half (nothing on it is ever BLK_FLAG_CLAIMED -- see
|
||||
* has_ever_claimed's own doc comment on blk_dev_slot_t), so the scan ran
|
||||
* to completion -- the ENTIRE drive, mostly cache misses over slow
|
||||
* emulated USB/BOT -- every single idle tick, forever. Live-caught:
|
||||
* this alone turned a WIREBIND identity's own thumbdrive attach into a
|
||||
* many-real-minutes stall that never resolved on its own. Two
|
||||
* independent fixes, both real hardening not just papering over the
|
||||
* trigger: has_ever_claimed skips the scan entirely (O(1)) for any slot
|
||||
* nothing has ever claimed (the common case for a just-attached drive);
|
||||
* migration_scan_lbn bounds *any* slot's per-tick cost regardless, via a
|
||||
* resume cursor that picks up where the previous tick left off instead
|
||||
* of restarting from start_lbn every time -- restores this function's
|
||||
* own "coarse cadence, cheap early-exit" design intent (this file's own
|
||||
* older doc comment above) for every device, not just the one it used
|
||||
* to hardcode. */
|
||||
#define MIGRATION_SCAN_BUDGET 256u
|
||||
|
||||
/* Per-slot half of blk_migration_idle_check() -- extracted so the caller
|
||||
* can walk every attached slot uniformly (below) instead of hardcoding
|
||||
* first_disk_slot(). Bounded, resumable scan -- see MIGRATION_SCAN_
|
||||
* BUDGET's own doc comment above for why. */
|
||||
static void blk_migration_idle_check_slot(blk_dev_slot_t *slot) {
|
||||
if (!slot->has_ever_claimed) return;
|
||||
|
||||
uint32_t range_start = slot->start_lbn;
|
||||
uint32_t range_end = slot->start_lbn + slot->user_blocks;
|
||||
if (range_end <= range_start) return;
|
||||
|
||||
if (slot->migration_scan_lbn < range_start || slot->migration_scan_lbn >= range_end) {
|
||||
slot->migration_scan_lbn = range_start;
|
||||
}
|
||||
|
||||
uint32_t hot_lbn = 0, free_lbn = 0;
|
||||
int have_hot = 0, have_free = 0;
|
||||
uint32_t lbn = slot->migration_scan_lbn;
|
||||
uint32_t examined = 0;
|
||||
|
||||
for (uint32_t lbn = slot->start_lbn;
|
||||
lbn < slot->start_lbn + slot->user_blocks && !(have_hot && have_free);
|
||||
lbn++) {
|
||||
while (examined < MIGRATION_SCAN_BUDGET && !(have_hot && have_free)) {
|
||||
uint32_t rel_pbn = lbn_to_slot_pbn(slot, lbn);
|
||||
if (slot_pbn_pack_offset(rel_pbn) != 0) continue;
|
||||
|
||||
blk_meta_t meta;
|
||||
if (blk_get_meta(lbn, &meta) != BLK_OK) continue;
|
||||
|
||||
if (!have_hot && (meta.flags & BLK_FLAG_CLAIMED) &&
|
||||
!(meta.flags & BLK_FLAG_MIGRATING) &&
|
||||
meta.write_count >= MIGRATION_WEAR_THRESHOLD) {
|
||||
hot_lbn = lbn;
|
||||
have_hot = 1;
|
||||
}
|
||||
if (!have_free && devblock_is_free(slot, lbn, &meta)) {
|
||||
free_lbn = lbn;
|
||||
have_free = 1;
|
||||
if (slot_pbn_pack_offset(rel_pbn) == 0) {
|
||||
blk_meta_t meta;
|
||||
if (blk_get_meta(lbn, &meta) == BLK_OK) {
|
||||
if (!have_hot && (meta.flags & BLK_FLAG_CLAIMED) &&
|
||||
!(meta.flags & BLK_FLAG_MIGRATING) &&
|
||||
meta.write_count >= MIGRATION_WEAR_THRESHOLD) {
|
||||
hot_lbn = lbn;
|
||||
have_hot = 1;
|
||||
}
|
||||
if (!have_free && devblock_is_free(slot, lbn, &meta)) {
|
||||
free_lbn = lbn;
|
||||
have_free = 1;
|
||||
}
|
||||
}
|
||||
examined++;
|
||||
}
|
||||
lbn++;
|
||||
if (lbn >= range_end) lbn = range_start;
|
||||
}
|
||||
slot->migration_scan_lbn = lbn;
|
||||
|
||||
if (have_hot && have_free && hot_lbn != free_lbn) {
|
||||
(void) blk_meta_relocate_devblock(hot_lbn, free_lbn);
|
||||
}
|
||||
}
|
||||
|
||||
/* FABRIC-3.md, 2026-09-09: was hardcoded to first_disk_slot() (whichever
|
||||
* device happened to attach first -- always Artemis's own virtio disk,
|
||||
* kernel_main.c attaches her unconditionally before anything else can).
|
||||
* Every other block_subsystem.c entry point (blk_subsys_attach_device()
|
||||
* chief among them) treats every attached slot uniformly through the
|
||||
* common blkio_dev_t/blkio_vtable_t interface (blkio.h), device type
|
||||
* never inspected; this was the one place that didn't, a bespoke
|
||||
* Artemis-disk special case bolted onto an otherwise device-agnostic
|
||||
* file. Fixed to walk the full slot chain -- every attached device now
|
||||
* gets the same wear-leveling scan, none hardcoded, none skipped. */
|
||||
void blk_migration_idle_check(void) {
|
||||
blk_dev_slot_t *slot;
|
||||
for (slot = g.head; slot; slot = slot->next) {
|
||||
if (!slot->dev) continue;
|
||||
blk_migration_idle_check_slot(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/* ===== weak hook (for main.c) ===== */
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
__attribute__((weak))
|
||||
|
||||
Reference in New Issue
Block a user