Attempts 7 and 11 both failed at exactly the same spot: process_alive read
'ok' within milliseconds of Popen, then http_health hit ECONNREFUSED in the
same second and gave up. Real cause: process_alive only confirms the
process EXISTS, not that backend_server.py has finished importing modules
and actually bound its listen socket -- a real, non-zero startup gap. The
old check_http_health did one urlopen(timeout=X) call; that timeout only
bounds how long to wait for a response once connected, not how long to
wait for the port to start accepting connections at all -- ECONNREFUSED
fires essentially instantly when nothing's listening yet, regardless of
timeout.
This touches an explicit 03-C design statement ("不重试——重试属于给坏版本
第二次机会"), so confirmed with the user before changing it rather than
just reinterpreting the policy unilaterally. Resolution: tolerating normal
startup latency within ONE health-check pass isn't "retrying a failed
check" -- check_process_alive already does exactly this (polls up to 30s
for the pid to exist). http_health now does the same, polling within its
own timeout for connection-refused-class errors specifically. A genuine
non-2xx HTTP response is still a definitive answer from a live server and
still fails immediately, no polling -- that boundary is unchanged.
Two new real regression tests: a real server that starts listening 0.3s
late is correctly waited for and passes; a real 500 response still fails
immediately without being mistaken for "not up yet".
564 tests green (441 src + 123 supervisor).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>