Host — the two remaining app-wide stall mechanisms plus main-thread pins
found by mining all nine hang reports:
- LinuxProcess.startStdinRelay wrote to a BLOCKING stdin fd on a
width-limited cooperative-pool thread, non-cancellably; wedged guests
starved the whole concurrency runtime (decode loops, watchdogs — an
app-wide freeze surviving the reconcile fix). Writes now offload to a
per-process GCD queue (vendored patch #18).
- TranscriptWriter (actor) did blocking write/fsync on the cooperative
pool; it now runs on its own DispatchSerialQueue executor.
- UserMessageBubble's truncation probe typeset entire pasted-log-sized
messages through CoreText per layout pass (100% main-thread pins in
the 07-21 hang reports); certainly-long messages now skip the probe
and render a prefix while collapsed.
- toolGroupSignature JSON-encoded every tool input in the transcript up
to 12.5x/s on the MainActor; now a structural hash. The summary pass
is trailing-throttled to 0.4s, and flatItems joins streaming chunks
once instead of re-copying the prefix per delta.
- StatusFeedFetcher.parseDate allocated three formatters per call (86%
of a pool thread in the 07-26 report); now shared statics.
Guest (vminitd) — teardown data loss and epoll registration hazards:
- IOPair no longer closes on a bare EPOLLHUP with a backpressure flush
in flight (dropped the CLI's final output line); EPOLLOUT finishes the
flush, then EOF closes loss-free. ManagedProcess.setExit closes only
stdin, letting stdout/stderr self-close on EOF, with an 8s grace pass
(patch #16).
- Epoll events carry a registration generation; the supervisor ignores
stale events for recycled fd numbers. registerFd refuses EEXIST
instead of clobbering the existing handler. TerminalIO's stdin relay
writes a dup of the terminal fd so its backpressure registration
can't collide with the stdout relay's (patch #17).
- VsockProxy flushes bytes parked toward the surviving peer on hangup,
closes the dialing socket on a failed backend connect, and
StandardIO/TerminalIO clean up partially-created pairs on setup
failure (patch #16).
Full suite: 1451+292+74+20 tests, two failures — both pre-existing
environmental (MacVM base image absent on this machine; a load-flaky
liveness test that passes 3/3 in isolation).
Co-Authored-By: Claude Fable 5 <[email protected]>
Host (dominant): AppStore.reconcileLocks polls every 3s on the MainActor
while any lock is held — effectively forever, since interrupted/errored
sessions deliberately retain locks. Each pass ran heldPathDisposition's
diverges() as three held×unmerged scans with two split-allocations per
pathsOverlap call, pinning the main thread for tens of seconds per pass
on a diverged trunk (hang-reports 2026-07-28: 100% of samples in
reconcileLocks→heldPathDisposition→pathsOverlap). That froze running
sessions' transcripts and starved the spawn path into the 60s
"produced no output" watchdog. pathsOverlap is now allocation-free
bytewise comparison with identical semantics, and divergentHeldPaths
answers all three questions from one O((held+unmerged)·depth) set.
Guest (persistence): VsockProxy threaded ONE offset pair through BOTH
relay directions; once the EAGAIN-return backpressure patch let pending
bytes persist, traffic in the other direction skewed the shared
counters, made the write leg unreachable, and spun the single
ProcessSupervisor poller thread forever — container-wide dead control
plane until VM recreation, triggered by exactly the backpressure the
host hang created. Each direction now owns its own pipe and counters
(OSFile.RelayDirection), and source EOF is only surfaced after the
pipe drains so SHUT_WR can't truncate a parked tail. Vendored patch
docs updated (#15); inert until the initfs image is rebuilt+repointed.
Co-Authored-By: Claude Fable 5 <[email protected]>
Switch the containerization dependency from the github URL to a vendored copy
(third_party/containerization, upstream commit 6b7b42ca) referenced by path, so
we can carry a small local patch that upstream lacks: LinuxContainer.Configuration
gains a `vmExtensions` field forwarded into VMConfiguration.extensions. Upstream
already supports VMConfiguration.extensions + the VZInstanceExtension hook, but
LinuxContainer — our only entry point — never forwarded them, so there was no way
to attach a device (e.g. a memory balloon) to a container's VM.
Tests/, docs/, examples/, images/ and the corresponding test targets are trimmed
for footprint (we never build the dependency's tests). See PATCHES.md for the full
diff vs. upstream and the re-vendoring procedure. Also adds the ContainerizationExtras
product to NucleicCore (AddressAllocator, named in the configureVZ signature).
Co-Authored-By: Claude Opus 4.8 <[email protected]>