• Stefy Lanza (nextime / spora )'s avatar
    one page waiting for a pod stopped the whole server (v0.4.16) · 0e6c5a49
    Stefy Lanza (nextime / spora ) authored
    A 5,000-document run rented 127 pods, reached $79/hr and completed zero
    documents in ten minutes. The front had stopped answering entirely:
    /v1/runpod/status timed out on every port, which is also what the client
    had been reporting as thousands of timeouts.
    
    The pods were fine. Probed from inside the container, both transports
    answered /healthz in milliseconds, vLLM was loaded on each one, and the
    GPUs sat at KV 0.0% with no surya worker line, because no page had ever
    reached them.
    
    `placement.choose` is synchronous and ends in a pool's `acquire()`, which
    waits on a condition variable for up to its default 1200 s and may rent a
    pod INLINE -- a candidate sweep over every region, then a boot wait, in
    whichever thread asked. It was called straight from `_read_on_pods`'s
    coroutine, so it ran on the event loop and took the whole process with it
    for as long as one page waited for a pod.
    
    It has been there since the pod pipeline was written (0.3.70) and cost
    nothing while acquire() returned at once: a pool with a free pod answers in
    microseconds. It turns fatal exactly when the pool is the thing under
    strain, which is when it matters most.
    
    So the lease runs on the pod pool now, and because a pod takes MINUTES to
    boot, three things follow:
    
      * One attempt is bounded (`POD_LEASE_ATTEMPT_S`, 20 s), threaded through
        `placement.choose(acquire_timeout=)` to `pool.acquire(timeout=)`. An
        unbounded attempt parks a thread for a whole boot, per page; bounded, the
        waiting happens in `_lease_a_pod`'s own loop where it is free.
      * `ocr.pod_pipeline_wait_s` goes 60 s -> 600 s. The 60 s was chosen because
        a waiting page held a thread, so a long ceiling meant thousands of parked
        threads. It no longer does, and at 60 s a cold start handed its whole
        first wave of pages to a GPU-less orchestrator to rasterise -- the exact
        failure `prefer` exists to prevent.
      * The per-page PDF split moves to the DEFAULT executor, not the pod pool:
        pdfium permits one caller at a time, and putting a split behind thousands
        of leases waiting for a boot is a cold start that cannot produce the next
        page while it waits for the pods that page is for.
    
    The forward itself stays on the event loop, which is the point of 0.4.8 and
    is now covered by a test that fails if it moves back.
    
    All of this is invisible to a test that awaits one page at a time: one page
    blocking the loop for 0.5 s looks exactly like one page taking 0.5 s. The new
    tests measure the longest gap between two ticks of a coroutine running
    alongside, and the thread-seconds held while waiting out a boot. Five of them
    fail on 0.4.15.
    Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01KnfkxBpcZ1v6ypRFFyhrVM
    0e6c5a49
Name
Last commit
Last update
..
cache Loading commit data...
__init__.py Loading commit data...
acceleration.py Loading commit data...
capabilities.py Loading commit data...
gpu_lock.py Loading commit data...
gpu_query.py Loading commit data...
grammar.py Loading commit data...
hf_loading.py Loading commit data...
manager.py Loading commit data...
parser.py Loading commit data...
peft_compat.py Loading commit data...
pipeline_cache.py Loading commit data...
placement.py Loading commit data...
quant.py Loading commit data...
ram_monitor.py Loading commit data...
templates.py Loading commit data...
text_loras.py Loading commit data...
thermal.py Loading commit data...
tmp_janitor.py Loading commit data...
tool_call_grammar.gbnf Loading commit data...
turboquant.py Loading commit data...
utils.py Loading commit data...
video_geometry.py Loading commit data...
weight_stores.py Loading commit data...