Skip Granian's unexpected-exit log for clean worker exits

Merged
FA
11d ago
+52-2

Fixes #7266. Alternative to #7457.

Problem

A group SIGTERM (or SIGINT) of a dev reflex run reaches the Granian worker and the supervisor at the same time. The worker runs its own signal handler and exits 0. The supervisor is blocked in the watchfiles reloader, so its handler has not run yet and interrupt_by_parent is not set when the per-worker watcher thread joins the worker. Granian then logs [ERROR] Unexpected exit from worker-1.

#7457 checks exitcode == -SIGTERM and interrupt_signal. In a real shutdown the worker exits 0 and the flag is usually unset, so that check never matches.

Fix

A worker process exits 0 only from its own SIGINT/SIGTERM handler. The watcher now treats exit code 0 as a requested stop and skips Granian's watcher. Crashes keep a non-zero or negative exit code and are still reported. Thread workers (free-threaded builds) have no exit code and keep Granian's handling. The socket is still released when no worker is left serving. Granian's watcher only logs in this case, because Reflex sets reload_ignore_worker_failure=True.

Validation

Full reflex run (frontend + backend), blank template, Python 3.11.15, Granian 2.8.3, Linux, group SIGTERM after the app is ready:

BuildClean shutdowns that log the error
main8 / 10
#745710 / 10
this PR0 / 10 (plus 0 / 5 on the rebased branch)

Worker SIGKILLed, then the app stopped: the crash is logged once, and the shutdown adds no error.

One unit test checks that a worker exiting 0 skips Granian's report and a SIGKILLed worker still reaches it. It fails on main.