Skip to content

terminal: return free-listed page memory to the OS - #13245

Merged
mitchellh merged 1 commit into
mainfrom
push-qssyxpsswnxt
Jul 8, 2026
Merged

mitchellh merged 1 commit into
mainfrom
push-qssyxpsswnxt

Conversation

@mitchellh

Copy link
Copy Markdown
Contributor

This uses madvise flags on both macOS and Linux to mark our unused pool items as free memory and not show up in RSS (until they're actually used). We can do this since we do page-aligned/page-multiple allocations, heyo!

Background: The PageList page pool never returns memory to the OS: destroyed pages are zeroed and free-listed until the surface exits. Any operation that shrinks the page count (clearing scrollback, pruning churn, resets, reflow) therefore retains its high-water RSS forever. Clearing a full scrollback keeps all of it resident, which at the default 10MB scrollback-limit is 10MB per terminal of memory that can never be used again for anything else.

Lots of memes on the internet about this, and it turns out operating systems give us an answer for this (both Linux and macOS at least), so let's do it kids.

On Linux, madvise(MADV_DONTNEED) reclaims the pages immediately and guarantees zero-fill on the next touch, which also lets us skip the zeroing memset entirely, making destroy cheaper.

On macOS, we zero in place and mark the item MADV_FREE_REUSABLE, which removes it from the process footprint immediately. Reuse is paired with MADV_FREE_REUSE when the pool hands a buffer back out so that footprint accounting stays correct. The zero invariant required by page reuse holds either way: reusable page contents are either preserved (our zeroes) or reclaimed and zero-filled by the kernel.

Other platforms and test builds keep the existing memset behavior.

LLM Notes

Fable 5 found the retention behavior while re-analyzing scrollback memory, wrote the change and tests, and verified the madvise semantics empirically with memory probes on macOS and a real Linux kernel, plus before/after throughput benchmarks on both. I reviewed the analysis, the diff, rewrote the code to be more idiomatic Zig, and wrote this commit message you're reading.

The PageList page pool never returns memory to the OS: destroyed pages
are zeroed and free-listed until the surface exits. Any operation that
shrinks the page count (clearing scrollback, pruning churn, resets,
reflow) therefore retains its high-water RSS forever. 

Clearing a full scrollback keeps all of it resident, which at the default 10MB
scrollback-limit is 10MB per terminal of memory that can never be
used again for anything else.

Lots of memes on the internet about this, and it turns out operating
systems give us an answer for this (both Linux and macOS at least),
so let's do it kids.

Pool items can't be individually freed since they live inside arena
chunks, but they are page-aligned and page-multiple sized, so we can
decommit them while they sit in the free list. Our page-aligned
allocation pays off, again!

On Linux, madvise(MADV_DONTNEED) reclaims the pages immediately and
guarantees zero-fill on the next touch, which also lets us skip the
zeroing memset entirely, making destroy cheaper.

On macOS, we zero in place and mark the item MADV_FREE_REUSABLE, which
removes it from the process footprint immediately. Reuse is paired
with MADV_FREE_REUSE when the pool hands a buffer back out so that
footprint accounting stays correct. The zero invariant required by
page reuse holds either way: reusable page contents are either
preserved (our zeroes) or reclaimed and zero-filled by the kernel.

Other platforms and test builds keep the existing memset behavior.

## LLM Notes

Fable 5 found the retention behavior while re-analyzing scrollback
memory, wrote the change and tests, and verified the madvise semantics
empirically with memory probes on macOS and a real Linux kernel, plus
before/after throughput benchmarks on both. I reviewed the analysis,
the diff, rewrote the code to be more idiomatic Zig, and wrote this
commit message you're reading.
@mitchellh
mitchellh requested a review from a team as a code owner July 8, 2026 03:54
@ghostty-bot ghostty-bot Bot added the vt Control sequence related label Jul 8, 2026
@mitchellh mitchellh added this to the 1.4.0 milestone Jul 8, 2026
@mitchellh

Copy link
Copy Markdown
Contributor Author

Btw, verified this on both macOS and Linux by filling scrollback and doing a clear screen. Without this PR, memory shows up in top/footprint/etc. After this PR, it doesn't.

@mitchellh
mitchellh merged commit ad692f1 into main Jul 8, 2026
181 checks passed
@mitchellh
mitchellh deleted the push-qssyxpsswnxt branch July 8, 2026 03:59
wesm pushed a commit to kenn-io/ghosthub that referenced this pull request Sep 1, 2026
- Reduce long-lived Ghosthub memory retention while pinned to libghostty 1.3.1.
- Release Metal and IOSurface resources while terminal surfaces are hidden, without detaching tmux or SSH clients ([Ghostty #14017](ghostty-org/ghostty#14017)).
- Track terminal page ownership explicitly and return free-listed page memory to the OS instead of retaining the process high-water footprint ([Ghostty #13241](ghostty-org/ghostty#13241), [Ghostty #13245](ghostty-org/ghostty#13245)).
- Build on the ten upstream lifetime fixes already merged in #220; record the exact upstream commits and bump the bootstrap schema so stale artifacts rebuild.
- Keep the v1.3.1 adaptation limited to hidden-surface semaphore and display-link APIs; the page-list source changes match upstream.
- Allow local debug builds to be inspected with macOS memory tools; packaged release entitlements remain unchanged.
- The patched libghostty passes its focused PageList tests, Ghosthub’s full Swift and Python suites, formatting checks, and the app build.

Co-authored-by: CL Kao <clkao@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

vt Control sequence related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant