Two cell_limits e2e tests failed intermittently on spindle CI (dbfe565, 54edef2). Both failures reproduce on a loaded Linux host, and both are fixed here.
test_a_cell_whose_threads_cannot_be_stopped_restarts_the_kernel. The Linux footprint was the resident set from /proc/self/statm. On a host short of memory, the kernel's pages are swapped out (zram) or reclaimed, so the resident set fell below the cap ("78-96 MiB held, cap 96 MiB"). stop_threads then treated the kernel as back under its cap and kept it, although a stuck thread still held the memory. This is a product problem: the same effect lets a hog that is being swapped out escape the cap altogether. footprint() on Linux now counts RssAnon + RssShmem + VmSwap from /proc/self/status: anonymous memory, resident or swapped out, without file pages. The perf gates already count anonymous plus swapped memory for the same reason. The test's mapping is now MAP_PRIVATE, so its pages stay counted when swapped. Darwin is unchanged.
test_a_cell_whose_threads_pass_the_cap_is_interrupted_and_the_namespace_kept. This one is a test problem. When the CPU is contended, the pool workers that have already started reach the allocation limit (RLIMIT_DATA) while fan_out is still starting the rest. A thread started at the limit dies before it sets _started, so Thread.start() waits forever in a C lock wait. The stop can't reach that thread, so after RESTART_AFTER the kernel restarts as designed, and the namespace is lost. The stack of the stuck thread was submit -> _adjust_thread_count -> Thread.start -> _started.wait. The hog's workers now wait on an Event that is set once pool.map has submitted all eight. robot-docs/kernel.md notes the Thread.start case.
Testing, on focalor (16 cores, zram). The load is N CPU burners, with "mem" meaning the run sits in a systemd scope with MemoryHigh=450M; x is the number of exclusive lanes:
- fan-out test, 64 burners, x=8: 52/60 before, 60/60 after. 48 burners + mem, x=4: 30/30 after.
- stuck-thread test, 48 burners + mem, x=4: 18/30 before (every failure is the CI signature), 30/30 after. 64 burners, x=8: 30/30 after.
- standalone fan-out repro on 2 CPUs: 12/200 hung before, 0/400 after.
- whole cell_limits file, idle and under memory load: 14/14 each.
- test/quick.sh and ./test.sh pass on macOS.
Note: this description was written by an agent (bunny's capflake child). A human may rewrite it in their own words.