- a child is fds: Child {id, stdin, stdout, stderr}, driven by the
existing net verbs (echo leg proves cat round-trip through write_dl/
read_dl); caller owns the fds, the runtime owns pid + pidfd
- wait_dl parks on the pidfd: code on exit, nil at the deadline with the
child untouched; one waiter per id, a second refuses by name; stale
ids refused via a generation counter in the handle
- proc.signal through pidfd_send_signal; actor_die kills the streaming
children the dying actor owns; dead fibers cannot linger as waiters
- ids 97-107 registered wholesale (wob.h, loader arities, dispatch
bound); Child + Signal predeclared records in types.ml; unimplemented
ids trap at the default case until their task lands
- test_proc 168/0 (echo, wait trio, one-waiter refusal, 200-round churn
fd-flat), suite ASan clean, woc-test green
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 9be87f159f1bf9cdd509ceed160e7ea518fde46c)
- ceiling: 32 fibers hold live sleepers; the 33rd spawn traps WO_T_IO
naming the ceiling; destroy sweeps all 32 (waitpid -1 = ECHILD after)
- unwind: a fiber parked on a live child is reaped at main's return and
the child dies with it (nchildren 0 straight after the call)
- churn: one thousand sequential `true` runs through a bytecode loop —
fd count flat, every slot released
- stop: SIGTERM from a helper 200 ms into a sleep-10 child answers rc 1
(STOPPED) with no surviving child
- test_proc 128 pass 0 fail in 2.6 s, suite ASan clean
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- proc.run_dl reachable: dispatch range extended to id 96 (builtin.c) and
the loader arity table gains [WO_B_PROC_RUN_DL] = 6 — without both, the
builtin answered "unknown stdlib builtin" (WO_T_EXPLICIT)
- deadline leg: sleep 10 vs 100 ms deadline traps WO_T_IO naming the
deadline in ~120 ms; the pid is gone (waitpid -1 = ECHILD) and the fd
count is flat; a worker fiber completes WHILE main is parked — the
shard was never blocked
- cap legs: stdout and stderr caps trap naming "cap 1000", child dead
- argv multi carries a drop entry at the run pc: a trapping run frees it
(LeakSanitizer caught the miss)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- deadlock proven first: chatty child (200 KB stdout, stderr held open)
hung the old sequential drain 5.0 s into the alarm, code -1, stdout
truncated at 8192; the leg demands completion under 4 s
- rework: nonblocking pipe read ends + pidfd_open behind one epoll fd the
fiber parks on (the _dl retry mould); both pipes drain on readiness, so
the deadlock is gone structurally — leg passes in 15 ms
- wo_child slot table in wo_vm (32/shard) carries cross-park state; caps
refuse by name (kill + WO_T_IO), deadline armed via dl_active/dl_at,
defaults 30 s / 1 MiB / 64 KiB
- WO_B_PROC_RUN_DL = 96 shares the case (per-call deadline_ms/out_cap/
err_cap; compiler row lands in a later task)
- fib_reap kills a reaped fiber's child; wo_vm_destroy sweeps the table
- raw syscalls for pidfd_open/pidfd_send_signal: glibc 2.35 build floor
has no wrappers
- all 19 suites green under ASan+UBSan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- new suite runtime/test/test_proc.c (auto-globbed by the Makefile)
- three legs against today's behavior: echo exits 0 with exact stdout,
false exits 1, a missing command answers 127 (the execvp convention)
- record fields copied out before the vm dies; ASan clean
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>