Skip to content

fix(terminal): cap active block height and skip redundant SumTree rebuilds#9738

Open
monotykamary wants to merge 4 commits intowarpdotdev:masterfrom
monotykamary:monotykamary/fix-pty-throughput-block-height
Open

fix(terminal): cap active block height and skip redundant SumTree rebuilds#9738
monotykamary wants to merge 4 commits intowarpdotdev:masterfrom
monotykamary:monotykamary/fix-pty-throughput-block-height

Conversation

@monotykamary
Copy link
Copy Markdown

@monotykamary monotykamary commented May 1, 2026

Description

What

Two feature-flagged optimizations to fix terminal hangs under high-throughput output (e.g. git clone):

  1. CompactProgressLines — Caches the last computed active block height and skips the block_heights SumTree rebuild when it hasn't changed between frames. Also consolidates FairMutex acquisitions in the wakeup handler to reduce contention with the PTY reader thread.
  2. CapActiveBlockHeight — Placeholder flag for future work to cap the active block's rendered height at the viewport/scroll layer (not the stored SumTree height — capping the stored height breaks scroll math since block_list_viewport.rs uses block_heights().summary().height for scroll position and follow-bottom anchoring).

Why

When a long-running command like git clone produces large amounts of output, every frame update_live_block_height() calls block.height()output_grid_displayed_height(), then rebuilds the block_heights SumTree by slicing + recombining. This causes FairMutex<TerminalModel> contention between the PTY reader thread (which holds the lock while parsing up to 64KB of ANSI bytes) and the UI thread (which holds it during block height computation). When both compete, the PTY reader can block on the lock, causing kernel PTY backpressure on the child process — effectively stalling git clone entirely.

This is the root cause behind the "screen rendering locks up under extreme stdout/stderr load" issue (#3626), as well as related reports of Warp hanging with Claude Code (#8409), extreme memory usage (#8205), and general slowness (#6743).

How

CompactProgressLines (feature flag, enabled for dogfood):

  • Added last_active_block_height: Option<Lines> cache to BlockList.
  • In update_live_block_height(), when the block is still executing and the computed height matches the cached value, early-return without rebuilding the SumTree.
  • This handles the common case during CR-based progress output (e.g. git clone progress bars) where \r overwrites the same line in-place without adding rows.
  • Cache is cleared when the block finishes, transitions to a non-executing state, or on resize.
  • In handle_terminal_wakeup(), merged three separate model.lock() acquisitions into a single lock scope, reducing FairMutex contention windows.

CapActiveBlockHeight (feature flag, placeholder — not currently wired):

  • An earlier revision capped the stored block height in the SumTree to viewport rows. This was removed because capping the stored height causes the viewport/scroll calculations (which use block_heights().summary().height) to disagree with the renderer (which uses the block's actual grid height). Once output exceeds the cap, follow-bottom treats the block as viewport-sized and new output becomes invisible.
  • The flag is retained as a placeholder for future work that could apply the cap at the rendering/viewport layer instead of the stored height.

Impact

Scenario Before After (flags enabled)
git clone (CR-based progress output) O(log N) SumTree rebuild per frame O(1) cache hit → early return
Normal command output O(log N) per frame O(log N) per frame (unchanged — cache misses when height changes)
Block finishes O(log N) for final height O(log N) for final height (cache cleared)
Lock acquisitions per wakeup 3 separate 1 consolidated

Workarounds (without this fix)

  • git clone --no-progress ... — suppresses progress output entirely
  • GIT_PROGRESS_DELAY=9999 git clone ... — delays progress output until completion

Linked Issue

Screenshots / Videos

N/A — performance fix with no visible UI changes. The behavioral change is that git clone and similar high-throughput commands no longer hang the terminal.

Testing

  • cargo check --features cap_active_block_height,compact_progress_lines — passes
  • cargo fmt -- --check — passes
  • cargo clippy — no new warnings
  • 51 blocks unit tests — all pass
  • Manual: cargo run --features compact_progress_lines, then git clone https://github.com/torvalds/linux.git — completes without hang

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

Related upstream issues: #3626, #8409, #8205, #6743

CHANGELOG-BUG-FIX: Fixed terminal hangs under high-throughput output (e.g. git clone) by skipping redundant layout rebuilds and consolidating FairMutex acquisitions.

…uilds

When a long-running command (e.g. git clone) produces large amounts of
output, the active block's displayed height grows unboundedly. Every
frame, update_live_block_height() traverses the entire DisplayedOutput
BiMap and rebuilds the block_heights SumTree, which is O(R) per frame
where R can be 10,000+ rows. This causes FairMutex contention between
the PTY reader thread and the UI thread, stalling the terminal.

Add two feature flags behind which we gate the fixes:

1. CapActiveBlockHeight: caps the reported height of an executing block
   to viewport rows. The full output remains in FlatStorage for
   scrollback; only the SumTree entry is capped. When the block
   finishes, the final height is computed from full grid contents.

2. CompactProgressLines: caches the last computed active block height
   and skips the SumTree rebuild when it hasn't changed between frames.
   This handles the common case during CR-based progress output where
   carriage returns overwrite the same line in-place without adding
   rows. Also consolidates three separate FairMutex acquisitions in
   handle_terminal_wakeup() into a single lock scope, reducing
   contention with the PTY reader thread.

Refs: warpdotdev#3626, warpdotdev#8409, warpdotdev#8205, warpdotdev#6743

Co-Authored-By: Warp <agent@warp.dev>
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have the users @monotykamary on file. In order for us to review and merge your code, each contributor must visit https://cla.warp.dev to read and agree to our CLA. Once you have done so, please comment @cla-bot check to trigger another check.

@github-actions github-actions Bot added the external-contributor Indicates that a PR has been opened by someone outside the Warp team. label May 1, 2026
@monotykamary
Copy link
Copy Markdown
Author

@cla-bot check

@cla-bot cla-bot Bot added the cla-signed label May 1, 2026
@cla-bot
Copy link
Copy Markdown

cla-bot Bot commented May 1, 2026

The cla-bot has been summoned, and re-checked this pull request!

…match

The initial commit introduced two bugs caught by cargo check:

1. The BlockList constructor was missing its closing brace (}),
   breaking the struct literal and function scope.

2. update_live_block_height() was pushing Lines into
   BlockHeightItem::Block which expects BlockHeight(Lines).

Also invalidates the last_active_block_height cache on resize,
since the CapActiveBlockHeight cap depends on viewport rows and
block heights change on reflow.

Co-Authored-By: Warp <agent@warp.dev>
@monotykamary monotykamary marked this pull request as ready for review May 1, 2026 14:53
@oz-for-oss
Copy link
Copy Markdown
Contributor

oz-for-oss Bot commented May 1, 2026

@monotykamary

I'm starting a first review of this pull request.

You can view the conversation on Warp.

I completed the review and posted feedback on this pull request.

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Copy link
Copy Markdown
Contributor

@oz-for-oss oz-for-oss Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overview

This PR adds feature-flagged terminal performance changes that cap active block height and skip redundant block height SumTree rebuilds, plus consolidates some terminal wakeup model locking.

Concerns

  • The active block height cap updates only the SumTree entry while rendering and several viewport helpers continue to use the block's full height. That makes scroll/follow-bottom coordinates inconsistent while a command is still running, so a long active block can be treated as viewport-sized and clipped from the top instead of showing the newest output until it finishes.
  • Supplemental security pass found no security-specific issues in the inlined diff.

Verdict

Found: 0 critical, 1 important, 0 suggestions

Request changes

Comment /oz-review on this pull request to retrigger a review (up to 3 times on the same pull request).

Powered by Oz

Comment thread app/src/terminal/model/blocks.rs Outdated
monotykamary and others added 2 commits May 1, 2026 22:03
…oll math

Capping the stored block height in the block_heights SumTree caused the
viewport/scroll calculations (which use the SumTree total) to disagree
with the renderer (which uses the block's actual grid height). Once
output exceeded the viewport cap, follow-bottom treated the block as
viewport-sized and the renderer clipped from the top — users would stop
seeing new output until the command finished.

Remove the cap from update_live_block_height() entirely. The
CompactProgressLines cache (which skips the SumTree rebuild when the
height hasn't changed between frames) is sufficient to solve the
performance problem without breaking scroll/viewport math.

Keep the CapActiveBlockHeight feature flag as a placeholder for future
work that could apply the cap at the rendering/viewport layer instead
of the stored height.

Co-Authored-By: Warp <agent@warp.dev>
Copy link
Copy Markdown
Author

@monotykamary monotykamary left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Capping the stored block_heights SumTree height breaks the scroll/viewport math since block_list_viewport.rs uses block_heights().summary().height for scroll position calculations and follow-bottom anchoring. Once output exceeds the cap, the viewport treats the block as viewport-sized and new output becomes invisible.

Fixed in d6d3832: removed the CapActiveBlockHeight cap from update_live_block_height() entirely. The CompactProgressLines cache (which skips the SumTree rebuild when the height hasn't changed between frames) is sufficient to solve the performance problem without breaking scroll math. Kept the flag as a placeholder for future work that could apply the cap at the rendering/viewport layer instead of the stored height.

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

Labels

cla-signed external-contributor Indicates that a PR has been opened by someone outside the Warp team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant