Skip to content

Release 19.0.0

Choose a tag to compare

@github-actions github-actions released this 31 Jul 00:45
· 6 commits to main since this release
Immutable release. Only release title and notes can be modified.
v19.0.0
a013c06

This release closes several ways a child process could be leaked, and several ways a task's promise
could be dropped. It is major because some fixes change runtime behavior for every consumer, even
though no API was removed or renamed.

  • 💔 BREAKING: outstanding work now keeps the event loop alive.

    Everything this library owns is deliberately unref'd, so an idle cluster never stops a script from
    exiting. But unsettled work held nothing open either: when the loop drained, beforeExit fired
    and end() tore down a task the caller was still awaiting. A queued task's promise was
    abandoned entirely — node exited 0 with no error at all — and an assigned task was rejected
    with "Process terminated before task completed". The only thing that ever prevented this was the
    per-task timeout timer, which doesn't exist at the default taskTimeoutMillis of 0.

    This is longstanding, not new in v19. An idle cluster still holds nothing, so the unrefStreams
    contract is unchanged. One consequence: if a child wedges and you haven't set taskTimeoutMillis,
    your process now stays alive rather than exiting silently. A visible hang beats losing work
    without a diagnostic, but it is a good reason to set that option.

  • 💔 BREAKING: end() is a barrier. It previously drained the pool and resolved, which is not
    the same as "every child is gone": an in-flight processFactory() could hand back a live child
    afterwards, and a recycling process could still be seconds into its graceful shutdown. Either way,
    await bc.end(); process.exit(0) could orphan a child.

    Explicit end() now waits for in-flight spawns and recycling, so it can take longer than it used
    to — and deliberately has no deadline, because until a factory returns we cannot know whether it
    already spawned a child. A factory that never settles keeps explicit shutdown pending.

    Automatic cleanup on beforeExit stays bounded by spawnTimeoutMillis, since process exit can't
    wait forever on an opaque factory. When that bound expires, every child known to be alive is
    force-killed and any task still waiting on those terminations is rejected — unless you set
    cleanupChildProcs: false, which means you handle PID cleanup yourself.

  • 💔 BREAKING: end() rejects tasks still queued, with
    BatchCluster.end() was called before this task could be assigned: <command>. Nothing settled
    them before, so calling end() with work outstanding now yields rejections where you previously
    got promises that never settled.

  • 💔 BREAKING: maxFailedTasksPerProcess defaults to 0 (disabled), and works when you set it.

    failedTaskCount was never incremented, so the previous default of 2 never recycled anything.
    Rather than silently enabling that rule for everyone, it is now off by default: a rejected task
    usually means bad input rather than a sick child, and it counts failures over the process's whole
    lifetime, so a long-lived child would be recycled after any two bad inputs, ever. If you set it
    explicitly, expect more churn and "broken" in childEndCounts. Prefer healthCheckCommand if
    you can ask the child directly.

  • 💔 BREAKING: kill() returns false on EPERM instead of throwing, matching how it already
    handled ESRCH; unrecognized codes still throw. Callers signal lists of pids in loops, and one
    pid we aren't permitted to signal stranded every pid after it.

  • 💔 BREAKING (TypeScript only): killProcessGroup is a required property of the exported
    InternalBatchProcessOptions and CombinedBatchProcessOptions types, so code building either by
    hand needs the new field. Passing options to the BatchCluster constructor is unaffected.

  • ✨ Added the killProcessGroup option (default false) and exported killGroup().

    Enable it if your processFactory uses detached: true, which makes each child its own process
    group leader: shutdown then keeps ownership of that group even after the leader exits, so
    grandchildren are cleaned up too. Safe but pointless for non-detached children — the group signal
    simply fails and we signal the child directly. Windows has no POSIX process groups and ignores it.

  • 🐞 Fixed two outright child-process leaks. If new BatchProcess() threw after the factory
    returned a live child — a factory whose stdio omits stdin or stdout, or a childStart listener
    that throws — the child never entered the pool, leaving it invisible to pids(), recycling,
    end(), and the exit backstop, one orphan per spawn retry, surviving even the parent's exit. And
    a child spawned while end() was already draining joined a pool nothing would drain again.

  • 🐞 The exit backstop now tracks every child spawned and not yet seen to exit, rather than current
    pool membership, so an abrupt process.exit() during shutdown or recycling can't leave one
    behind. This supersedes the PID-snapshot workaround from v17.3.1.

  • 🐞 A consumer-supplied logger that throws can no longer strand a child: cleanup now runs before
    the log, and a termination that rejects for any reason force-kills the child before it is treated
    as finished.

  • 🐞 vacuumProcs() catches per-process end() rejections, as closeChildProcesses() always has.
    Its only caller discards the promise, so a throwing childEnd listener became an unhandled
    rejection — killing the host process and orphaning every child then mid-recycle.

  • 🐞 An IPC disconnect is no longer treated as an exit. It only means the channel closed, but it
    made running() false, which skipped both the SIGTERM and the SIGKILL. Children from an
    IPC-enabled factory (stdio: [..., "ipc"] or fork()) that disconnect while running are now
    terminated.

  • 📦 The published package no longer includes the spec suite's subprocess fixtures (*-helper.js).
    They require dist/test.js, which was already excluded, so they were broken weight in the tarball.

Commits

  • fix(ProcessPoolManager)!: harden child and task shutdown lifecycles (67046a9)
  • chore: update dependencies for globals and npm-check-updates to latest versions (6bfba33)
  • chore(.npmignore): add subprocess fixture exclusions for spec suite (830cbaf)
  • chore(build): update Node.js version matrix to 22,24,26 in build workflow. (52397d6)
  • chore: update CodeQL action versions to v4.37.4 (ddc6fb2)
  • fix(ProcessTerminator): stop tests from signalling real pids (0c2aac3)
  • chore(fmt) (575c02a)
  • chore: update dependencies in package.json (30ee9e1)
  • chore: update GitHub Actions to use latest action versions (1145fe8)
  • chore: update deps (49e7057)
  • chore: rename precommit npm script to all (cf100a4)