Skip to content

fix: bound hot-path costs found by a performance scan - #98

Merged
simota merged 4 commits into
mainfrom
fix/perf-scan-high-findings
Oct 14, 2026
Merged

simota merged 4 commits into
mainfrom
fix/perf-scan-high-findings

Conversation

@simota

@simota simota commented Oct 14, 2026

Copy link
Copy Markdown
Owner

A static performance scan found four places where cost grew without a bound on hot paths. This fixes them.

  • Blank-line floods bypassed scrollback-limit. Rows that trim to zero cells were charged 0 bytes and never filled a page, so yes '' grew history and memory forever. Each row is now charged its RowMeta size, and pages also seal on a row-count cap.
  • Busy background tabs cost O(K²) main-thread Terminal locks. Each Redraw from an occluded pane re-titled every dirty occluded window. Titles are now re-asserted only for windows leaving occlusion; still-occluded windows already refresh their own title on their own Redraw.
  • Remote panes posted one main-thread Redraw per WebSocket frame. Each Redraw locks the terminal and rebuilds the session card. Remote frames now go through the local path's redraw floor (decide_redraw_floor) with a trailing redraw.
  • Overtaken remote scrollback snapshots were refetched in a tight loop. Each refetch cost a connect, a server-side tail walk, and two full local scrollback builds. The first refetch stays immediate; later ones back off exponentially from 1s to 30s.

Reviewer notes:

  • feed_remote_bytes no longer requests a redraw; run_connected decides it via remote_redraw_decision. Three existing tests asserted one redraw per fed batch; those assertions were removed because that is the behavior being changed.
  • Under synchronized output, the trailing redraw is anchored to the latest batch (now + SYNCHRONIZED_OUTPUT_MAX_SUPPRESSION), as before this branch. Anchoring it to the last redraw request lets it land inside the render side's snapshot hold window, where it would be swallowed.
  • Overtaken refetches are not capped. Raw output can overtake two fetches in a row and a later fetch still merges, so giving up would drop older history for the whole connection.
  • Not addressed here: main-thread reflow of every pane on font-size, sidebar, or scale changes, and the O(rows × pages) PagedScrollback::row lookup. Both need a reflow-path redesign and are left for a follow-up.

Validation:

  • cargo test --workspace --exclude noa-pty --exclude noa-ipc: 2,543 passed, 0 failed. noa-ipc tests need loopback bind and noa-pty tests need openpty; both were unavailable in the sandbox, and neither crate is changed.
  • New regression tests: blank-row flood stays within the limit; remote redraws are floored during a 2,000-frame flood; the synchronized trailing redraw is anchored to the latest batch; overtaken refetch backs off instead of spinning; scrollback still merges after two overtaken fetches; backoff delay schedule.
  • Each new regression test was confirmed to fail with its fix reverted and to pass with it applied.
  • cargo clippy --workspace --all-targets shows no new warnings. rustfmt --check on changed files and git diff --check passed.

Limitations:

  • Static analysis only; no profiling or GUI run. The size of the main-thread stalls was not measured.
  • The occluded-tab title change has no App-level test harness and was verified by reading the code.

Rows that trim to zero cells were charged 0 bytes and never filled a page,
so a blank-line flood (yes '') grew history past scrollback-limit forever.
Charge each row its RowMeta size and seal pages on a row-count cap too.
Each Redraw from an occluded pane looped refresh_window_title over every
dirty occluded window, so K busy background tabs cost O(K^2) Terminal locks
per second on the main thread. Only re-assert titles for windows leaving
occlusion; still-occluded ones already refreshed on their own Redraw.
Remote panes posted one main-thread Redraw per WS frame with no floor; each
locks the terminal and rebuilds the session card. Coalesce through the local
path's decide_redraw_floor with a trailing deadline.

An overtaken scrollback snapshot was refetched unconditionally, so a pane
whose local history already covers the tail reconnected in a tight loop.
Refetch at most once per generation.
…erable

Under synchronized output the trailing redraw was anchored to the last
redraw request, so it could land inside the render side's snapshot hold
window and be swallowed, leaving the final batch unpainted. Anchor it to the
latest batch again.

Capping overtaken refetches at one also gave up on races a later fetch
would win, leaving older history missing for the whole connection. Keep
refetching, immediately once and then with exponential backoff (1s..30s).
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T01:41:00.898920Z 765bf21 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@simota
simota merged commit 928eb31 into main Oct 14, 2026
1 check passed
@simota
simota deleted the fix/perf-scan-high-findings branch October 14, 2026 01:49
@simota simota mentioned this pull request Oct 14, 2026
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