Skip to content

fix: hand loaded files to the screen without sleep-polling - #5

Merged
backnotprop merged 1 commit into
mainfrom
fix/load-handoff
Oct 27, 2026
Merged

backnotprop merged 1 commit into
mainfrom
fix/load-handoff

Conversation

@backnotprop

Copy link
Copy Markdown
Contributor

Problem

Loading the full Linux v6.0→v7.0 patch (76,872 files, 711 MB, SHA-256 510693d1… per fixtures/diffshub/corpus.json) took 12–44 s to reach all files. Parse alone (inspect --summary) takes about 5 s. A sample during loading showed the parser thread asleep about 90% of the time.

The cause was the handoff in src/input/worker.rs. Each file went through a sync_channel(2), and the parser slept 5 ms whenever the channel was full. The screen loop took the two files and then waited for the next wake. On a busy machine, loading time was set by thread wake-up latency, not by parsing.

Change

  • src/input/worker.rs: replaces the reply channel with a mailbox (Mutex<VecDeque> + Condvar).
    • Files are bounded by count (1,024) or index bytes (8 MiB), whichever comes first. One oversized file can always enter an empty queue, as LARGE_DIFFS.md requires.
    • When the queue is full, the parser blocks on the condition variable until the screen drains it. There is no sleep.
    • Prepared sources stay limited to 2, because each one holds two complete source sides.
    • Completion and errors are never blocked.
    • close() marks the mailbox closed, which wakes a blocked parser with Cancelled, and drops the queued replies.
    • try_reply keeps its behavior: Disconnected still means the worker exited and nothing remains.
    • The new try_replies takes every queued reply at once.
  • src/engine.rs: poll takes every queued input reply on each wake, instead of up to 64 replies from a queue that held only 2. The queue bound now limits how much work one turn does, so a keypress is still handled after each turn.

The intent of the bounded queue is unchanged (LARGE_DIFFS.md §6 and DELIVERY_REPORT): published files are bounded, and backpressure pauses ingestion, not keyboard input. Queued files are the same Arc<FileIndex> that the document keeps, so the larger bound adds no measurable peak memory (see below).

Measurements

The A/B runs were interleaved on the same machine (macOS, 18 cores, shared and busy: load average 13–17, with unrelated long-running processes). There were 6 runs per arm and both binaries were warmed first. Each run is a real 160×50 PTY using the repository's tools/terminal_baseline.Session/Frames.

  • "All files" is the first synchronized frame whose footer no longer reads "Loading". This is the corpus_acceptance criterion.
  • The keypress probe toggles help with ? during loading and measures the time until a frame shows the change.
metric (median, range) main 5856855 this branch
load avg (1 min) at start 14.3 (13.2–17.4) 15.7 (13.4–16.6)
time to all 76,872 files 32.80 s (28.17–37.93) 5.87 s (5.33–8.33)
first text 8.7 ms (8.2–10.3) 10.7 ms (7.0–22.1)
sampled peak RSS (process group) 402 MiB (207–410) 406 MiB (244–411)
max RSS (rusage) 409 MiB (327–413) 410 MiB (349–413)
keypress → frame while loading 4.4 ms (1.2–16.4), p95 7.4, n=250 4.8 ms (2.3–7.6), p95 7.2, n=46
viewer CPU user+sys 6.69 s 5.65 s

The first-text difference is within noise. One branch run took 22 ms, and the other five took 7.0–13.6 ms.

Parse baseline (tools/parse_baseline.py, branch binary): all 5 fixtures exit 0 and their git totals match. Linux takes 4.74 s with a 386 MiB peak. With this change, interactive loading is close to the parse time.

Checks

  • cargo fmt --check
  • cargo clippy --all-targets (default and --all-features), and cargo clippy --lib --no-default-features, all with -D warnings
  • cargo test --release --locked --no-fail-fast -- --test-threads=1
  • The embedded host contract tests

Tests:

  • New unit tests cover the count and byte bounds, a producer blocked on a full queue that resumes after a drain, and close releasing a blocked producer.
  • Two tests/input_worker.rs tests assumed the two-file queue. They now assert that all files queue without the screen draining, and they still check that published sources remain valid and are released after close.

The input worker published each file through a two-slot channel and slept
5 ms whenever it was full, while the screen took the two files and waited
for the next wake. On a busy machine, loading the Linux v6.0..v7.0 patch
spent most of its time asleep: about 30 s to reach all files against a
5 s parse.

Files now go through a queue bounded by 1,024 files or 8 MiB of index
bytes. When the queue is full, the parser blocks on a condition variable
until the screen drains it. The screen takes everything queued on each
wake. Prepared sources stay limited to two, because each holds both
complete sides. Closing the worker wakes a blocked parser.
@backnotprop
backnotprop merged commit ad63220 into main Oct 27, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant