• Stefy Lanza (nextime / spora )'s avatar
    front: an in-flight request that leaks freezes the install, so stop leaking one (v0.3.27) · cbd51a4a
    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: 's avatarClaude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01PxYHWbCAjjjD7A1rhFKtaW
    cbd51a4a
__init__.py 4.21 KB