Skip to content

exec can orphan the child process tree when halted during acquisition #423

Description

@taras

Problem

exec from @effectionx/process@0.8.1 can leave the child process tree running with no owner when the calling scope is halted during acquisition. createPosixProcess spawns the OS process and then suspends several times — two fromReadable resources and four spawns — before registering the ensure that signals the process group and joins stdio EOF. A halt landing on any of those suspension points discards the pending instructions, so the kill-ensure never registers and the child runs to completion as an orphan.

This was one of the two causes of #417: an orphaned deno bundle wrote its output into a scratch directory after the scope's cleanup had already removed it.

Two smaller defects sit in the same teardown:

  • The catch (_e) { /* process is probably already dead */ } around process.kill(-pid, "SIGTERM") also swallows the join: when the kill throws, the all([stdoutDone, stderrDone]) wait is skipped entirely, so teardown can return while the tree is still winding down.
  • On macOS, killpg on a process group whose members are all zombies fails with EPERM, not ESRCH (verified empirically on Darwin 25.5.0). Code distinguishing "already dead" from "not permitted" by errno misclassifies this case.

What the repository does meanwhile

scripts/lib/contained-run.ts (from the #417 fix) registers the terminate-and-join teardown before the process exists and creates the process in the same synchronous continuation, so no halt can separate acquisition from release. The bundler is its only consumer; every other exec call site still has the upstream window.

Acceptance

  • @effectionx/process registers the child's termination with no suspension point after the process is created, or upstream ships an equivalent guarantee, and the pinned version here carries it.
  • scripts/lib/contained-run.ts is retired in favor of the upstream primitive, its tests migrated to whatever pins the upstream contract.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions