Repository navigation
perf(cache): check cache entries off the scheduler thread - #982
Merged
wan9chi merged 2 commits intoNov 9, 2026
Merged
Conversation
The scheduler polls every task's future on one thread, so the synchronous work in a task's cache lookup held up every other task: hashing its explicit inputs, decoding its local entry or the entries fetched from the remote cache, and validating them, which stats and hashes every inferred input. Tasks that became ready together had their lookups run one after another. In a CI run where 26 cached Vite builds became ready at once, all remote hits, they started over about 1 second on Linux and up to 2.7 seconds on Windows instead of together. That work now runs on rayon's thread pool, in the task's span, so the lookups of ready tasks proceed concurrently, with the same results and miss reasons. The pool is rayon's rather than tokio's blocking threads: validation already hashes inputs in parallel on it, and its size keeps the checks from crowding out downloads and extraction. On a 4-core Windows runner, running all 26 checks at once on blocking threads made those builds finish later than before. A lookup still stops when the run is cancelled: work that hasn't started doesn't run, and work that's already running finishes in the background with its result dropped. Tasks that become ready together now start in the order their checks finish. The e2e harness runs vp with RAYON_NUM_THREADS=1, so the checks finish in the order they're submitted, and those tasks start in scheduling order in snapshots. The new e2e cases hold the reads of a task's inferred input while its local or remote entry is checked, and release them from a task that the scheduler can only start once it sees another task finish, so a check on the scheduler's thread would never finish. vtt hold-reads holds the reads with a FIFO on Unix and with an exclusive oplock on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fspy benchmarklinuxmacoswindows |
…-thread Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
The scheduler polls every task's future on one thread. The synchronous work in a task's cache lookup ran on that thread, so it held up every other task:
Tasks that became ready together looked up one after another, and nothing else made progress in the meantime.
In a CI replay of a monorepo, 26 cached Vite builds become ready at once, all of them remote hits. Their starts were spread over about 1 s on Linux and up to 2.7 s on Windows. Per run, entry validation took up to 1.9 s (Linux) and 3.6 s (Windows) of scheduler-thread time when most tasks hit. When most missed, it took 3.7–4.8 s, because each miss checks up to 8 remote entries.
Changes
run_off_thread. Hits, misses and miss reasons are unchanged. Cancelling the run still stops the lookup: work that hasn't started doesn't run, and work that's already running finishes in the background with its result dropped.try_hitreturnsNonewhen a check is cancelled.RAYON_NUM_THREADS=1, so the checks finish in the order they're submitted and snapshots keep scheduling order. With random 0–30 ms delays injected into the checks, the full suite passed 11 of 11 runs this way, while 8 threads failed 8 of 10.cache_check_concurrency, covers a local and a remote entry. It holds the reads of a task's inferred input while the entry is checked, and releases them from a task that the scheduler can only start once it sees another task finish. A check on the scheduler thread would never finish, and the step would time out.vtt hold-readsholds the reads with a FIFO on Unix and with an exclusive oplock on Windows.vtt barrierexposes its wait for reuse.🤖 Generated with Claude Code
Note
Medium Risk
Touches core cache lookup, cancellation, and scheduling paths; behavior should match prior hit/miss semantics but concurrent validation and ordering when multiple tasks start together may surface edge cases.
Overview
Cache lookups no longer block the DAG scheduler. When multiple tasks become ready together, input hashing, local entry decode/validation, and remote entry resolution now run on rayon's pool via new
run_off_thread, so other tasks can keep making progress instead of queuing one lookup after another on the single scheduler thread.try_hitandlookup_cachewere updated to use sharedValidationstate and an optional result layer: cancelled checks returnOk(None)/Report::Cancelledinstead of blocking or mis-reporting.PathGlobSet::into_ownedsupports sending negative globs into that off-thread work.E2E coverage adds
cache_check_concurrency(blocked file reads during validation), plusvtt hold-reads(FIFO on Unix, oplock on Windows) and a reusablebarrier::wait. The harness setsRAYON_NUM_THREADS=1so snapshot task order stays stable.Reviewed by Cursor Bugbot for commit ff367a1. Bugbot is set up for automated code reviews on this repo. Configure here.