Pipes decided end-of-file outside their lock
Status: FIXED (2026-09-11). An external review reported the write half; the read half, which it mentioned only as "ideally", was the more serious.
The write half
hw_pipe_write tested pp->readers == 0 before taking g_pipe_lock, while
hw_pipe_release decrements readers under it. A writer could read one reader,
the last reader close on another core, and the writer then enqueue anyway:
write returned the full length with no SIGPIPE and no EPIPE, for bytes
nobody could read.
The read half, which was worse
hw_pipe_read found the buffer empty under the lock, released it, and only then
tested writers == 0 - unlocked - to decide end of file. A writer could enqueue
and close in between, and the reader returned end of file with those bytes still
in the buffer: lost, silently, in the shape of an ordinary pipeline such as the
boot script's ls /EFI/BOOT | wc -l.
The fix
Each decision is made in the critical section that holds the data it depends on,
with no new lock. The reader computes end of file - nothing copied, nothing
faulted, no writers - before it unlocks. The writer tests readers right after
taking the lock and before enqueuing, raising SIGPIPE after unlocking.
A missed wakeup between the reader's unlock and its sti; hlt is still possible
and still harmless: hlt returns on the next timer tick, so it costs at most one
tick of latency.
What was not done
No red test. Both windows are a few instructions between two cores, and a loop racing a short write against a close would pass nearly every time - a test that cannot fail proves nothing, and the case file would have to say so. The fix is structural and is argued from the code.
check.sh all green with the boot's pipelines (BUSYBOX_SH_OK, PIPE_OK).
Twelve boots: 11 pass, 1 fail - the THREADS four-worker crash family
(.boot-evidence/fail-20260911-162643-boot12.log, rip 0x405b72, panic), not a pipe.