Skip to content

Reclaim hidden terminal renderers on memory pressure - #7050

Merged
austinywang merged 5 commits into
mainfrom
issue-7039-idle-surface-memory
Jun 29, 2026
Merged

austinywang merged 5 commits into
mainfrom
issue-7039-idle-surface-memory

Conversation

@austinywang

@austinywang austinywang commented Jun 29, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Closes #7039.
References the master resource-accumulation tracker #5731.

This PR makes the existing non-destructive terminal renderer reclamation path react to macOS memory-pressure warnings. The normal timer still uses the configured idle/LRU policy. On a warning/critical DispatchSourceMemoryPressure event, cmux now asks RendererRealizationController to release every hidden realized terminal renderer immediately, while preserving the hard safety rule that visible terminals are never selected. The existing hidden-browser discard runs in the same callback afterward.

This deliberately builds on the renderer realization machinery already on main; it does not duplicate the broader shell-restart hibernation work from #5739. It also does not conflict with #6979/#6984, which tune/apply the same renderer-reclaim direction for canvas/default behavior.

Measurement Baseline

Captured on the running stable app without building locally:

The change targets the app-process graphics resident set. It does not claim to reduce child-process RSS from agents; that remains the #5731/#6313 child-process guardrail/workspace lifecycle problem.

Demo Video

Not attached. Per task instructions, the dev build must not be launched until after CI is green and the user explicitly approves the cloud build command.

Review Trigger

Memory/performance fix. Please review the renderer realization pressure path, visible-surface safety invariant, bounded retry behavior for dropped Ghostty mailbox enqueues, and Xcode project wiring for the new source file.

Validation

  • git diff --check
  • python3 scripts/swift_file_length_budget.py --write-budget
  • python3 scripts/swift_file_length_budget.py
  • ./scripts/lint-pbxproj-test-wiring.sh
  • ./scripts/check-pbxproj.sh
  • python3 scripts/check-package-resolved-policy.py
  • python3 scripts/check-workspace-package-groups.py --check
  • Bundled autoreview: clean, no accepted/actionable findings; cmux policy clean.
  • GitHub PR checks: all required checks green.

Per task instructions, I did not run local tests, reload.sh, reload-cloud.sh, or bare xcodebuild.

Checklist

Localization Audit

No user-facing strings, settings rows, docs text, schema text, alerts, menus, or tooltips were added or changed. The changed Swift surfaces are internal planner/controller/callback code plus tests, so no localization catalog updates are required.

@vercel

vercel Bot commented Jun 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 29, 2026 7:12pm
cmux-staging Building Building Preview, Comment Jun 29, 2026 7:12pm

@coderabbitai

coderabbitai Bot commented Jun 29, 2026 •

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-7039-idle-surface-memory

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.

@greptile-apps

greptile-apps Bot commented Jun 29, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hooks the existing RendererRealizationController into the PaneMemoryGuardrail's system memory-pressure callback to eagerly release all hidden terminal renderer GPU resources (Metal swap chain / IOSurface) on macOS warning/critical pressure events, while the normal scheduled idle/LRU policy is unchanged.

  • Planner: A new trigger parameter routes the pressure path to an O(n) linear filter — all hidden realized surfaces are selected, visible surfaces are never touched, and the enabled setting is respected.
  • Controller: On pressure the private evaluate overload calls releaseRenderer() on each selected surface; if the Ghostty mailbox drops the message (the rendererRealized mirror stays true), a bounded retry chain (two extra passes via deferred Task { @MainActor in }) re-attempts without entering an unbounded loop.
  • AppDelegate wiring: reclaimForSystemMemoryPressure(now:) is prepended to the existing onSystemMemoryPressure callback that already discarded hidden browser WebViews; the callback type is (@MainActor () -> Void)? so actor isolation is preserved end-to-end.

Confidence Score: 5/5

Safe to merge — the change is additive, non-destructive, and respects the hard invariant that visible surfaces are never touched.

The pressure path is a clean O(n) filter that reuses the proven planner/controller machinery. Actor isolation is correct end-to-end: the callback is typed @mainactor, the controller is @mainactor, and no new cross-isolation calls are introduced. The retry logic is sound because rendererRealized is updated synchronously on mailbox success, so the post-release check fires only for genuinely dropped messages and the retry count is bounded at two extra passes. The new tests cover the key planner decisions for pressure mode. No structural or correctness issues were found.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/RendererRealizationReclaimTrigger.swift New 7-line enum with two cases (scheduled, systemMemoryPressure); clean, single-purpose, correctly Sendable and Equatable.
Sources/App/RendererRealizationPlanner.swift Pressure path added as an O(n) early-return filter before the existing sorted warm-set logic; visible and unrealized surfaces are correctly excluded; no sort overhead on the hot memory-pressure path.
Sources/App/RendererRealizationController.swift Retry logic is sound — the rendererRealized mirror is updated synchronously on mailbox success, so the post-release check correctly fires only on dropped mailbox messages; actor isolation is correctly MainActor throughout; retry is bounded at 2 extra passes.
Sources/AppDelegate.swift One-line addition prepending reclaimForSystemMemoryPressure to the existing onSystemMemoryPressure (@mainactor) callback; no actor isolation concern.
cmuxTests/RendererRealizationPlannerTests.swift Two new pure-planner tests: pressure mode selects all hidden realized surfaces (visible and unrealized excluded), and disabled setting blocks selection; both test new behavior that would have failed before commit 2.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant OS as macOS Kernel
    participant PMG as PaneMemoryGuardrail
    participant RRC as RendererRealizationController
    participant RRP as RendererRealizationPlanner
    participant TS as TerminalSurface
    OS->>PMG: DispatchSourceMemoryPressure warning/critical
    PMG->>RRC: reclaimForSystemMemoryPressure(now:)
    RRC->>RRP: selectedSurfaceIds(trigger: .systemMemoryPressure)
    RRP-->>RRC: hidden realized surface IDs
    loop each selected surface
        RRC->>TS: releaseRenderer()
        TS-->>RRC: rendererRealized false on success, true on drop
    end
    opt mailbox drop detected
        RRC->>RRC: "Task @MainActor evaluate retries-1"
    end
    PMG->>PMG: discardHiddenBrowserWebViews()
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant OS as macOS Kernel
    participant PMG as PaneMemoryGuardrail
    participant RRC as RendererRealizationController
    participant RRP as RendererRealizationPlanner
    participant TS as TerminalSurface
    OS->>PMG: DispatchSourceMemoryPressure warning/critical
    PMG->>RRC: reclaimForSystemMemoryPressure(now:)
    RRC->>RRP: selectedSurfaceIds(trigger: .systemMemoryPressure)
    RRP-->>RRC: hidden realized surface IDs
    loop each selected surface
        RRC->>TS: releaseRenderer()
        TS-->>RRC: rendererRealized false on success, true on drop
    end
    opt mailbox drop detected
        RRC->>RRC: "Task @MainActor evaluate retries-1"
    end
    PMG->>PMG: discardHiddenBrowserWebViews()
Loading

Reviews (2): Last reviewed commit: "Retry pressure renderer reclaim on dropp..." | Re-trigger Greptile

@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 `@Sources/App/RendererRealizationPlanner.swift`:
- Around line 42-43: The memory-pressure fast path in
RendererRealizationPlanner’s planning logic is doing unnecessary ranking work
before returning all hidden realized surfaces. Move the `trigger ==
.systemMemoryPressure` branch ahead of the sort/ranking path in the relevant
planning method so this case bypasses the full `sorted` pass. Build the returned
`Set` directly from `inputs`/the hidden-surface filter for that trigger, keeping
the path near-linear and avoiding repeated filter/map/sorted work.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 7f8c8597-5c99-453e-ad3d-90af821c7034

📥 Commits

Reviewing files that changed from the base of the PR and between 39df334 and b6ef209.

📒 Files selected for processing (4)
  • Sources/App/RendererRealizationController.swift
  • Sources/App/RendererRealizationPlanner.swift
  • Sources/AppDelegate.swift
  • cmuxTests/RendererRealizationPlannerTests.swift

Comment thread Sources/App/RendererRealizationPlanner.swift Outdated

This branch was successfully deployed

1 active deployment
Preview – cmux — 73c0c194 Deployed Jun 29, 2026 by vercel[bot]
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.

WindowServer watchdog killed the entire GUI session under OOM (35.2/36 GB) — stable cmux app is the #1 process at 2.6 GB (2026-06-27 spindump)

1 participant