Terminate the frontend process tree when reflex run shuts down

Closed
FL
23d ago
+1628-24

When reflex run is started without a TTY (CI jobs, containers, process supervisors), SIGTERM or SIGINT stops the backend but the launcher hangs inside run_concurrently_context until something SIGKILLs the process group: the frontend dev server keeps running, its log-streaming thread never finishes, and the exit path waits on it forever.

What this changes:

  • Processes launched with new_process(..., run_managed=True) are tracked on a registry scoped to run_concurrently_context. When the run unwinds, each tracked child is terminated: on POSIX the frontend is started in its own session so its whole process group (bun and its node children) gets SIGTERM, with SIGKILL escalation after a timeout; on Windows the tree is terminated via psutil. Registration is opt-in, and only the frontend dev server opts in.
  • In frontend-only mode there is no backend server to install signal handlers, so a bare SIGTERM killed the launcher outright and orphaned the frontend. SIGTERM/SIGINT are now handled there so the run context unwinds, the frontend is terminated, and the launcher exits cleanly. A frontend task that fails still propagates its own exception instead of exiting 0.

I reproduced the hang on current main (launcher stuck after SIGTERM until SIGKILL), then reran with this change: all four combinations (frontend-only/fullstack, SIGTERM/SIGINT) exit 0 promptly with no bun/node/granian processes left behind.

Tests: uv run pytest -q tests/units/test_reflex.py tests/units/utils/test_processes.py tests/units/utils/test_exec.py (85 passed), ruff check/format, pyright.

Closes #6980