Scope
Complete VM shutdown ownership for Bun shell builtins whose work spans a graph of native worker-pool jobs rather than one self-contained job.
Tracked from #66 and the native WorkPool lifetime audit in #465.
Affected task graphs
ShellCpTaskstarts on the shared WorkPool, then parks whileShellAsyncCpTaskruns a directory scan and may spawn manySingleTaskworkers. The shell variant currently bypasses the VM native-job barrier.ShellLsTaskrecursively creates moreShellLsTaskworkers while walking directories.ShellRmTaskrecursively createsDirTaskworkers and can enqueue multiple owner-thread verbose-output completions before its final completion.
Each graph retains shell/interpreter state and an EventLoopHandle. Waiting only for the first worker callback is insufficient: a child worker can still touch the parent after the VM is destroyed. Holding one admission across a parked graph without a graph-level terminal signal can also deadlock shutdown.
Required ownership contract
- Admit the whole graph, or every child edge, into the creating VM's native-work barrier.
- Reject new child work after the barrier closes without leaving parent counters permanently pending.
- Publish every terminal owner-thread completion before releasing the last barrier admission.
- Add cancellation for every queued completion, including
ShellRmTask.DirTaskverbose completions, without calling shell state or JavaScript. - Release copied paths, errors, output buffers, file descriptors, keep-alive refs, arenas, and task graph allocations exactly once on normal completion, error, admission rejection, and VM shutdown.
- Preserve mini-event-loop behavior.
Regression coverage
Use the existing native WorkPool saturation probes from bun:internal-for-testing to force each graph to remain queued during Worker.terminate(). Assert teardown blocks until release, then terminates cleanly with exact cancellation/resource counters. Also run the exact pinned Bun cp, ls, and rm shell suites from /Users/chris/Code/bun at 4982b91e3702094330f3be3883354c52b8c01323.