Repository navigation
terminal: return free-listed page memory to the OS - #13245
Merged
Merged
Conversation
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.
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. |
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This uses
madviseflags 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.