WIREBIND: kill the orphaned console VM when the user VM birth fails

Found live during identity-heap-capacity testing (2026-09-07,
hotplugging Zuse + 8 identities one at a time and measuring the kernel
heap arena via a temporary allocator-stats probe, since reverted): every
WIREBIND identity attach births two VMs in sequence -- a "console" VM,
then the real "user" VM. When the second birth failed (arena
fragmentation under concurrent VM load, a separate, not-yet-fixed
capacity issue), capsule_wirebind_try_attach() logged the failure and
returned, but the console VM that had *already succeeded* was never
torn down. It stays live and registered under the identity's username,
consuming its own ~228KB of the fixed 4MB kernel heap arena forever --
nothing ever points a real user at it, since WIREBIND only ever hands
the caller the user VM's id.

This turns every failed identity attach into a permanent net loss of
heap rather than a neutral retry: confirmed live that a failed attach
left the arena 228,576 bytes worse off than before the attempt, and
every subsequent attempt starts from that worse baseline, compounding.

Fix: call capsule_vm_kill(username) on the now-orphaned console VM
before returning from the failure path -- the same teardown
capsule_wirebind_eject()/capsule_wirebind_unclean_detach() already use
elsewhere in this file (vm_cleanup() + sf_free(), confirmed live to
actually reclaim per-word dictionary allocations, FABRIC-3.md
§IX.2/§IX.3).

Verified live with the same allocator-stats probe (written, captured,
reverted -- not part of this commit): after the fix, a forced user-VM
birth failure now returns the arena to exactly its pre-attempt byte
count (3,526,256, matching the baseline precisely) instead of leaking
228,576 bytes. Three-arch clean qemu acceptance (single Zuse device,
the standard regression case) passed on amd64, aarch64, and riscv64.

The underlying capacity/fragmentation question (why the 7th concurrent
identity's arena allocation fails at all despite technically-sufficient
free bytes) is a separate, open architecture question -- not addressed
here. See project memory for the full measured numbers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78
This commit is contained in:
Robert Allan James
2026-09-07 11:37:06 -04:00
co-authored by Claude Sonnet 5
parent e10fb76fb2
commit d8a195b8d8
6 changed files with 26964 additions and 1 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated # Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-07T12:52:21Z --> <!-- Generated by mkcapsule --manifest 2026-09-07T15:26:51Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. --> <!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live --> <!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. --> <!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
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
+14
View File
@@ -185,6 +185,20 @@ void capsule_wirebind_try_attach(struct blkio_dev *dev,
if (capsule_runcap_birth(dev, sig, user_vm_name, mama_vm->stadium_vm_id, &user_id, if (capsule_runcap_birth(dev, sig, user_vm_name, mama_vm->stadium_vm_id, &user_id,
&user_ctx) != CAPSULE_RUN_OK) { &user_ctx) != CAPSULE_RUN_OK) {
console_println("WIREBIND: user VM birth FAILED"); console_println("WIREBIND: user VM birth FAILED");
/* The console VM above already succeeded and is live/registered
* under `username` -- without this, it's an orphan: nothing ever
* points a real user at it (WIREBIND only ever hands the caller
* the user VM's id), so it just sits there permanently holding
* its own kernel-heap arena allocation. Confirmed live 2026-09-07
* (identity-heap-capacity testing): this is exactly what turns a
* single failed identity attach into a permanent net loss of
* heap, compounding every subsequent attempt's odds of failing
* too, rather than a neutral retry. capsule_vm_kill() is the same
* teardown capsule_wirebind_eject()/capsule_wirebind_unclean_
* detach() already use elsewhere in this file -- vm_cleanup() +
* sf_free(), verified to actually reclaim per-word dictionary
* allocations (FABRIC-3.md §IX.2/§IX.3, 2026-09-06). */
capsule_vm_kill(username);
return; return;
} }