Skip to content

⚡ Bolt: [performance improvement] Avoid eager scrollback buffer allocations on the UI thread - #369

Open
Lucenx9 wants to merge 1 commit into
mainfrom
bolt-perf-embedded-scrollback-clone-8310152555995107913
Open

⚡ Bolt: [performance improvement] Avoid eager scrollback buffer allocations on the UI thread#369
Lucenx9 wants to merge 1 commit into
mainfrom
bolt-perf-embedded-scrollback-clone-8310152555995107913

Conversation

@Lucenx9

@Lucenx9 Lucenx9 commented Jul 22, 2026

Copy link
Copy Markdown
Owner

💡 What

This PR refactors embedded_spawn.rs to process persisted_scrollback without cloning it. By shifting the evaluation logic (embedded_scrollback_restore_bytes and should_skip_initial_embedded_scrollback_snapshot) inside the model lock closures (.and_then() and .is_some_and()), we can borrow the string slice via .as_deref().

🎯 Why

persisted_scrollback is a potentially very large string buffer (configured up to 64KB lines). Currently, whenever a terminal tab is restored or initialized in embedded mode, we extract this buffer from the model using an eager .clone(). This causes a heavy, slow, and completely redundant O(N) heap allocation right on the UI thread, which blocks rendering and introduces latency.

📊 Impact

  • Eliminates up to a 64KB string heap allocation per terminal initialization.
  • Lowers memory utilization spikes during application start or layout restoration.
  • Decreases blocking time on the UI thread, making tab creation feel snappier.

🔬 Measurement

Run cargo test -p forktty-ui-gtk --no-default-features to verify correctness. Profile the UI thread during startup with multiple persisted panes to see the eliminated String::clone allocations in embedded_spawn.


PR created automatically by Jules for task 8310152555995107913 started by @Lucenx9

Summary

  • Avoids cloning large persisted scrollback buffers during embedded terminal initialization and snapshot checks by borrowing them under the model lock.
  • Preserves existing scrollback restoration and snapshot behavior while reducing allocations and UI-thread startup spikes.
  • No changes to GTK/VTE behavior, socket/core Rust code, public APIs, or security/privacy handling.
  • Validation: cargo test -p forktty-ui-gtk --no-default-features.

In `embedded_spawn.rs`, we were extracting `persisted_scrollback` (which can be a large string buffer up to 64KB) out of the model `Mutex` using `.clone()`. This was causing significant and unnecessary O(N) heap allocations directly on the UI thread during terminal initialization.

This commit shifts the logic that requires the scrollback buffer into the critical section of the `Mutex` lock, using `.and_then()` and `.is_some_and()`. This allows us to safely borrow the large string via `.as_deref()` instead of cloning it, preventing the allocation entirely while maintaining safety and keeping the critical section small enough to avoid contention.

Co-authored-by: Lucenx9 <185146821+Lucenx9@users.noreply.github.com>
@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Embedded scrollback restoration and initial snapshot decisions now use lock-scoped borrowed persisted buffers instead of cloning strings. A dated guidance note documents avoiding eager clones of large UI data structures.

Changes

Embedded scrollback handling

Layer / File(s) Summary
Borrowed restoration and snapshot paths
crates/forktty-ui-gtk/src/gtk_app/controller/embedded_spawn.rs, .jules/bolt.md
Restoration and initial snapshot checks borrow persisted scrollback with as_deref; guidance documents lock-scoped access patterns for avoiding eager large allocations.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • Lucenx9/forktty#341: Addresses related eager Surface cloning with borrowed or field-level access patterns.

Suggested labels: frontend, rust, gtk, docs

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the performance-focused scrollback allocation change on the UI thread.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Privacy Boundary ✅ Passed Changed code only borrows local persisted_scrollback and updates model/session state; no telemetry or remote I/O was introduced.
Terminal Command Safety ✅ Passed PASS: changes only borrow persisted scrollback with as_deref inside locks; no PTY/socket/shell/path execution logic was added or changed.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-perf-embedded-scrollback-clone-8310152555995107913

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/forktty-ui-gtk/src/gtk_app/controller/embedded_spawn.rs`:
- Around line 488-497: Update should_skip_initial_embedded_scrollback_snapshot
in ghostty_gtk_embed.rs to require supports_restore_scrollback() before
returning true. Ensure the embedded_spawn.rs caller only skips the initial
snapshot when restoration is supported and the existing persisted scrollback
conditions also allow it; otherwise preserve the first fresh snapshot.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2cf5d622-c4df-4f5a-855a-50898c3ecf53

📥 Commits

Reviewing files that changed from the base of the PR and between b122448 and 9e434a7.

📒 Files selected for processing (2)
  • .jules/bolt.md
  • crates/forktty-ui-gtk/src/gtk_app/controller/embedded_spawn.rs

Comment on lines +488 to +497
// ⚡ Bolt: Avoid eager cloning of the potentially 64KB scrollback buffer by reading it inside the lock.
let mut skip_initial_snapshot = model.lock().ok().is_some_and(|model| {
let persisted_scrollback = model
.surface(&surface_id)
.and_then(|surface| surface.persisted_scrollback.as_deref());
should_skip_initial_embedded_scrollback_snapshot(
embedder.supports_restore_scrollback(),
persisted_scrollback,
)
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not skip the first snapshot when restoration is unsupported.

The restore callback is only registered when supports_restore_scrollback() is true, but should_skip_initial_embedded_scrollback_snapshot in crates/forktty-ui-gtk/src/gtk_app/ghostty_gtk_embed.rs ignores that argument. On older libraries, this can discard the first fresh snapshot and leave stale persisted scrollback in the model.

Make the predicate require restore support:

Proposed fix
 pub(super) fn should_skip_initial_embedded_scrollback_snapshot(
     supports_restore: bool,
     persisted_scrollback: Option<&str>,
 ) -> bool {
-    let _ = supports_restore;
-    persisted_scrollback.is_some_and(|text| !text.is_empty())
+    supports_restore && persisted_scrollback.is_some_and(|text| !text.is_empty())
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// ⚡ Bolt: Avoid eager cloning of the potentially 64KB scrollback buffer by reading it inside the lock.
let mut skip_initial_snapshot = model.lock().ok().is_some_and(|model| {
let persisted_scrollback = model
.surface(&surface_id)
.and_then(|surface| surface.persisted_scrollback.as_deref());
should_skip_initial_embedded_scrollback_snapshot(
embedder.supports_restore_scrollback(),
persisted_scrollback,
)
});
pub(super) fn should_skip_initial_embedded_scrollback_snapshot(
supports_restore: bool,
persisted_scrollback: Option<&str>,
) -> bool {
supports_restore && persisted_scrollback.is_some_and(|text| !text.is_empty())
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/forktty-ui-gtk/src/gtk_app/controller/embedded_spawn.rs` around lines
488 - 497, Update should_skip_initial_embedded_scrollback_snapshot in
ghostty_gtk_embed.rs to require supports_restore_scrollback() before returning
true. Ensure the embedded_spawn.rs caller only skips the initial snapshot when
restoration is supported and the existing persisted scrollback conditions also
allow it; otherwise preserve the first fresh snapshot.

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