Skip to content

Gracefully shut down child processes on quit#119

Open
gwleuverink wants to merge 6 commits into
mainfrom
feature/graceful-stop
Open

Gracefully shut down child processes on quit#119
gwleuverink wants to merge 6 commits into
mainfrom
feature/graceful-stop

Conversation

@gwleuverink
Copy link
Copy Markdown
Collaborator

Add a handlesOwnShutdown option for child processes and wait for graceful shutdown on quit

An app can spawn child processes that manage their own long-running children (queue workers, subprocesses, PTYs). Some of those know how to shut their children down cleanly: signal each one, let it flush or persist state, then exit. The current quit path stops them two ways:

  1. stopProcess tree-kills. It sends SIGTERM to the whole tree (killSync(pid, 'SIGTERM', true)), so a process's children die out from under it before it can wind them down in order.

  2. Quit doesn't wait. before-quit runs synchronously and quits on the same tick it issues the kills, so even a process that traps SIGTERM has no time to finish cleanup.

What changed

A handlesOwnShutdown option on ChildProcess (start / php / node / artisan). When set, stopping the process sends a single SIGTERM to that process alone instead of tree-killing its descendants, so it can bring its own children down on its own terms.

Important

The flagged process must trap SIGTERM and stop its children itself. If it doesn't, the default SIGTERM action still terminates it and leaves the children orphaned. That's why it's opt-in and defaults to false (the existing tree-kill, no handler needed), and why the name is what it is: it should be obvious at the call site who's responsible. On Windows, where signals can't be trapped, it falls back to tree-kill so children are never orphaned.

This needs to be documented clearly.

A graceful app quit. before-quit no longer quits immediately. It now:

  • stops the framework's own processes first (the PHP server and the like), so an incoming request can't boot the app and spawn fresh child processes mid-shutdown;
  • stops the app's child processes, honoring handlesOwnShutdown;
  • waits for them to exit before calling app.quit(), capped at 12s so a stuck process can't block the quit. Apps with no child processes quit immediately.

Notes

  • The 12s cap is fixed rather than configurable for now. A graceful wait didn't exist before, so any reasonable bound is an improvement, and nothing yet justifies a knob.
  • The fake records handlesOwnShutdown only when it's explicitly set, so existing assert* callbacks keep working without a major version bump (inline note on simplifying this in the next major).
  • dist/ is left out for CI to build.
  • Tests cover handlesOwnShutdown reaching the child-process API for each spawn method (and defaulting to false), plus a fake assertion.

gwleuverink and others added 6 commits June 2, 2026 09:21
if set skips tree-kill on app-quit so supervisors get a chance to spin down child processes
run pint & prettier locally. upstream rule defaults changed.
@gwleuverink
Copy link
Copy Markdown
Collaborator Author

I will update the docs once you've had a look at this

@gwleuverink gwleuverink self-assigned this Jun 2, 2026
@gwleuverink gwleuverink added the enhancement New feature or request label Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants