• Stefy Lanza (nextime / spora )'s avatar
    a lease may not wait in a thread (v0.4.19) · 72bbed69
    Stefy Lanza (nextime / spora ) authored
    Third run, third failure, and this one was mine. A 5,000-document run
    dispatched ZERO pages to 50 healthy pods in 41 minutes: not one
    `POST /v1/ocr` in any pod's log, every GPU at `KV 0.0%`, 811 pages waiting,
    and the scaler reading that unservable queue as demand and renting to 73
    pods at $43/hr. `/healthz` answered in 1.6 ms the whole time, so the loop
    fixes from 0.4.16/0.4.17 were holding and nothing looked wrong from outside.
    
    0.4.18 bounded a lease attempt at 20 s and ran it on a 256-thread pool. That
    made the LEASE STAGE the ceiling on everything: 256 threads at one attempt
    per 20 s passes ~13 pages a second however many pods are up, and a thread
    that drew the growth branch sat through an entire ~9-minute boot. Against
    811 pages each retrying every couple of seconds, nothing got through.
    
    So `acquire(grow_inline=False)`: answer now, or start growth in the
    background and raise `NoPodRightNow`. The caller waits on its event loop,
    where waiting is free, and `POD_LEASE_ATTEMPT_S` drops 20 s -> 2 s for the
    pools whose acquire predates the flag. Threaded through
    `placement.choose(grow_inline=)` by signature inspection, so a candidate
    that cannot take it keeps working.
    
    Demand had to survive that. `_waiting` only ever counted THREADS parked in
    acquire, and the non-blocking path has none, so the pool would have stopped
    growing. It now counts DENIALS over a window equal to the caller's retry
    interval, which makes the count distinct waiting PAGES rather than the
    attempts they have made between them -- a page retrying every 2 s appears
    once in any 2-second window. A longer window multiplies demand by the retry
    rate, which is how the same run read its own retries as new work.
    
    Two more things found on the way:
    
      * `pod_weight`/`pod_capacity`/`pod_threshold` depend only on a card's NAME,
        but were recomputed per POD per acquire -- `_pick` once per free pod,
        `_pods_for_demand` once per serving pod, `_preferred_vram_gb` walking
        every pod inside each -- all under the pool lock. Memoised per card:
        measured 286us -> 69us per acquire at 70 pods, 528us -> 102us at 128. The
        memo self-initialises via getattr, because a pool built without __init__
        must not have an optimisation turn into an AttributeError.
    
      * `_pick` returned a bare None for four different causes -- no pods,
        none healthy, all draining, all at their in-flight cap -- which need
        opposite responses, and the caller could only report the queue it could
        not drain. It now says which, once per 15 s, with every count in the line.
    
    I also mis-read `futex_do_wait` on 324 of 352 threads as lock contention. A
    condition-variable wait IS a futex wait, so that is what pages legitimately
    waiting for a pod look like. The pod logs settled it instead: zero POSTs
    means the dispatch never happened, not that it was slow.
    Co-Authored-By: 's avatarClaude Opus 5 <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01KnfkxBpcZ1v6ypRFFyhrVM
    72bbed69
Name
Last commit
Last update
..
admin Loading commit data...
api Loading commit data...
backends Loading commit data...
broker Loading commit data...
cluster Loading commit data...
compose Loading commit data...
frontproxy Loading commit data...
models Loading commit data...
ocr Loading commit data...
openai Loading commit data...
pydantic Loading commit data...
queue Loading commit data...
tasks Loading commit data...
util Loading commit data...
__init__.py Loading commit data...
cli.py Loading commit data...
config.py Loading commit data...
main.py Loading commit data...
platform_paths.py Loading commit data...
system_app.py Loading commit data...