-
Stefy Lanza (nextime / spora ) authored
coderai_engine_inflight / coderai_requests_active are load-bearing: the hourly autoupgrade's idle guard reads them to decide whether a restart is safe, and with MAX_DEFERRALS=0 a non-zero count makes it SKIP for ever. So one request counted in and never out silently stops a host taking upgrades, and the only symptom is a version that stops moving. Observed on the Digesta orchestrator today: the gauge read 13 against two real requests, the 09:01 tick logged "SKIP: 7 piece(s) of work in progress" and left the image alone, and only a manual restart cleared it. The structural leak is in the streaming proxy path. Starlette awaits a response's background task only AFTER the body is sent -- `await self.stream_response(send)` then `await self.background()` -- so when the client disconnects mid-stream the send raises, the exception propagates, and `BackgroundTask(_release)` never runs. The relay generator's `finally` DOES run in that case (Starlette closes the iterator) but not when the body is returned without streaming, so both now call `_release`, which is idempotent via a flag. `exit_request` is idempotent per rid as well, and that is required rather than decorative: two release paths firing for one request would otherwise decrement an UNRELATED in-flight request, and the count would under-report real work -- worse than over-reporting, because then a restart lands during a request. Every request is now timestamped, not just the inference ones that carry `meta`. Without that a leaked non-inference request was invisible as well as unreleasable: the count said 13 and the Tasks page showed nothing, which is how this went unnoticed. And a reaper underneath all of it (`server.inflight_max_age_s`, default 3600, 0 disables), called from the two places that ASK whether anything is in flight: * the /metrics scrape, because that is the exact moment a stale count does its damage -- the scrape that would report a lie is the right place to correct it, not to hope somebody notices; * the supervisor's drain wait, which would otherwise sit out the whole grace period for a request that finished an hour ago. An hour, not a minute: a cold RunPod pod can make a single OCR page take 14 minutes, and reaping a live request would be a worse bug than the one being fixed. Training is unaffected -- it is tracked as a JOB and the request that starts it returns at once. NOT fixed here, noted for later: `_proxy_passthrough` has the same BackgroundTask-only release for the system worker. It never calls enter_request, so it cannot leak the gauge -- it leaks an httpx stream, which is a smaller and separate problem. tests/test_front_inflight_leak.py: 14 tests, including double-release, late release after a reap, thread safety, and the source invariants for the two release paths. 299 passed across the front/metrics/registry/config/admin selection; the 9 failures are pre-existing (broker_config x5, broker_streaming, studio_composed_surfaces, whisper_server x2) and touch no file changed here. Co-Authored-By:Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PxYHWbCAjjjD7A1rhFKtaW
cbd51a4a