• Stefy Lanza (nextime / spora )'s avatar
    models page: stop paying for the scan on every open (v0.2.44) · bebe940d
    Stefy Lanza (nextime / spora ) authored
    The page was slow to open on a large configuration. Measuring first, because the
    obvious suspect was wrong: the disk scans are not the problem — scan_cache_dir
    over 59 repos is 0.42s and the GGUF walk 0.53s, and _scan_caches() is 0.08s once
    warm. The server-side stale-while-revalidate cache was already doing its job.
    
    What it was, in order of cost:
    
    THE CACHE DIED WITH THE PROCESS. It starts empty in every new one, and _cached()
    recomputes synchronously when it has nothing — so the first read after a restart
    pays the whole scan while someone sits looking at a spinner. Measured on the real
    configuration: 3.69s cold, 0.001s once the previous result is read back from
    disk. It is now persisted (atomically, under <config>/cache/) and seeded on first
    use. The stored SIGNATURE is deliberately not restored: it is a list of mtimes,
    and trusting it across a restart would serve a possibly hours-old list without
    revalidating. Leaving it unset means the stored result is returned instantly AND
    a refresh starts behind it. A missing, truncated or future-version file just
    falls back to scanning.
    
    THE PAGE WAITED ON ITSELF. First paint ran refreshLoadedStatus, then
    refreshQuantJobs, then the list — three round trips in a chain — while
    _loadEngineInfo did two more in another chain and then rendered the list a SECOND
    time. Inside the list load, /admin/api/cached-models and /admin/api/models also
    went out one after the other. Now: the two status reads go out together, the two
    list reads go out together, and concurrent loadCachedModels() calls collapse into
    one in-flight load, so first paint does one fetch and one render instead of three.
    A call made after it settles still refetches, which every post-action refresh
    relies on.
    
    IT PAINTED NOTHING UNTIL ALL OF THAT FINISHED. The last response is kept in
    sessionStorage and painted before any network call, then revalidated in place. It
    is a listing of what is on disk, so seconds of staleness cost nothing — and a
    quota error or private mode just means the page behaves as it does today.
    
    One thing the tests had to be corrected on: the first version asserted the
    background rescan had not run, which failed because _cached() starts the refresh
    thread and then reads state["data"] — with a trivial scan function the thread
    wins the race. That race is harmless (fresher data), and it was the assertion
    that was wrong: the property worth holding is that the CALLER is not blocked, so
    the test now uses a slow scan and asserts the read returns in under 0.2s with the
    stored value, and that the revalidation really did start.
    
    112 tests.
    Co-Authored-By: 's avatarClaude Opus 5 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_0182DhR1eFNPQyGmDrpHbedY
    bebe940d
test_model_scan_cache.py 5.31 KB