Skip to content

view blocks the render loop on I/O, freezing the TUI over HTTP #744

Closed

Description

@dfa1

vortex view <https url> freezes while it fetches data: the screen stops redrawing, keys (even q) do nothing, and the queued keys replay at once when the fetch ends. Locally it is barely visible; over HTTP (e.g. the Hugging Face samples, ~200 ms per range request) a chunk takes seconds.

Cause

The render loop waits on the I/O thread. IoWorker exists so the render loop never does I/O, but every frame reads its window synchronously:

VortexGridTui.Loop.render() → fetchWindow → LazyGridSource.readRows → worker.runAndAwait(...) (cli/src/main/java/io/github/dfa1/vortex/cli/tui/VortexGridTui.java:192, LazyGridSource.java:145)

So the main thread blocks until the worker has loaded and formatted the chunk. IoWorker.pending(), meant for a status-line "loading" counter, is never seen non-zero by a frame.

Over HTTP LazyGridSource.loadChunk (LazyGridSource.java:216) makes each wait long:

  • it decodes every column of the chunk (chunk.column(...) for all of them), one range request each, sequentially, though only the on-screen columns are drawn;
  • moving forward several chunks walks each one in between with iter.next();
  • moving back even one chunk closes the scan and walks again from chunk 0.

Proposal

  1. Never block the render loop. render() requests the window without waiting and draws what is cached, with … cells and a "loading" status for the rest; the loop keeps reading keys (q quits at once) and redraws when the worker completes. A request the user has already scrolled past is dropped, not queued.
  2. Fetch only the visible columns, concurrently. Scan with a projection of the on-screen columns (plus a small margin), with a bounded number of range requests in flight.
  3. Jump straight to a chunk. A row-range scan for the target chunk, instead of walking from the current position or restarting from chunk 0.

(1) fixes the freeze on its own; (2) and (3) make it fast. (2) shares the concurrent-range work with #743.

To check before (3): whether skipping a chunk with iter.next() fetches anything over HTTP or only advances the iterator (a JFR recording, io.github.dfa1.vortex.HttpRange events, while pressing End).

Reproduce

U=$(curl -s -o /dev/null -w '%{redirect_url}' https://huggingface.co/datasets/dfa11/vortex-java-samples/resolve/main/0.19.1/openpowerlifting/openpowerlifting.compact.vortex)
java -XX:StartFlightRecording:filename=view.jfr -jar cli/target/vortex-cli-*-all.jar view "$U"

Scroll with PageDown, press End, then q while it loads.

Related: #741, #742, #743.

Activity

  1. dfa1 commented on Nov 13, 2026

    @dfa1
    OwnerAuthor

    Done in #747: the render loop no longer blocks on I/O, only the visible columns are fetched (concurrently, merged requests). Item 3 (jump straight to a chunk) is not needed: skipping a chunk costs no requests, and a manual test over HTTP is fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingperf

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions