Skip to content

Terminal framebuffers are allocated and never released (0.1.24, distinct from #137) #155

Description

@amkyu

Summary

limux-host grows without bound at roughly 2.7 GB/day under normal use. After 5 days 11 hours of uptime the process holds 15.07 GB committed. Sampling the memory shows the bytes are RGBA terminal framebuffers — 88–98% of the two dominant regions is the repeating word 282c34ff, which is #282c34 (the terminal background) at full alpha.

This is not #137. That issue was Ghostty surfaces outliving their panes, and the fix in #138 works correctly here: thread trios stay balanced (6 renderer / 6 io / 6 io-reader against 6 live surfaces) for the whole run, and closing panes tears the surfaces down cleanly. Framebuffers leak anyway.

Environment

limux 0.1.24 (dpkg), binary built 2026-08-09 20:15
libghostty fork bvolpato/ghostty @ 81ab8ff (the submodule pin at the v0.1.24 build)
kernel 7.0.0-28-generic
hardware 62 GB RAM, 63 GB swap, NVIDIA GPU, GTK4
workload 4 workspaces, 6 surfaces, several long-running CLI agent sessions

Measurements

Fresh process started 2026-08-18 11:09 at 348 MB.

uptime 5d11h
  rss        296 MB
  swap    15,132 MB
  committed   15.07 GB

RSS alone hides this completely. At the measurement above the process shows under 300 MB resident while holding 15 GB. Everything is paged out. Any monitoring based on VmRSS will report a healthy process. VmRSS + VmSwap is the number that matters.

Growth is activity-driven. An 8-hour idle window showed committed memory flat to within 5 MB. Growth events correlate with whichever surface is focused during use.

The leak is one ever-growing region

Two mappings hold 13.7 of the 15.07 GB:

region          virt MB   rss MB  swap MB
77994f374000       7821        5     7815   [anon]
629ec12d6000       5745      157     5579   [heap]

The anonymous region grows by mremap, so its start address moves. Sizes recorded across the run, in order, never decreasing:

4045 4312 4769 5494 6104 6295 6371 6409 6943 7249 7363 7783 7821 7973 MB

Byte-level evidence

Sampled with a plain pread of /proc/<pid>/mem (no ptrace attach, so the process is never stopped), 4 MB each at head, middle and tail.

Anonymous region, 7821 MB:

sample   282c34ff   longest run   printable
head       97.8%       0.63 MB       74.0%
mid        88.1%       0.52 MB       73.9%
tail       94.4%       0.49 MB       71.6%

93.4% average across 12 MB sampled. Extrapolated over the region that is ~7.3 GB of background pixels, roughly 200 framebuffers at the ~40 MB size of a 4877x2050 RGBA surface.

[heap], 5745 MB:

head   53.5% 00000000, 68.3% zero bytes      allocator free space
mid    89.7% 282c34ff                        framebuffers, RGBA
tail   78.3% 342c28ff                        framebuffers, BGRA

Note the tail: 342c28ff is the same colour with the channel order reversed. Two pixel layouts are present in the leaked buffers, which suggests two producers — or a format-conversion staging buffer whose output is also retained.

The remaining top words in every framebuffer sample are UI greys (888888ff, 373737ff, 4b4d51ff, 9c9d9eff) and, in the BGRA tail, greens (002802ff, 004704ff). These are rendered screens, not blank allocations.

Small amounts of limux IPC JSON are interleaved:

"tion.create","params":{"body":"Claude needs your permission","subtitle

Negligible by volume against 93% pixels, but it confirms these regions are general allocation arenas rather than a dedicated framebuffer pool.

Ruled out

Each tested on a freshly restarted process where a step would have been obvious:

  • Scrollback. limux never sets scrollback-limit. Setting it to 5 MB in ~/.config/ghostty/config slowed the rate by roughly 40% but did not stop it. Streaming 54 MB of text through a terminal added 48 MB and then stayed flat for three minutes.
  • Layout resize. Three pane create/close cycles returned committed memory to baseline every time.
  • Workspace switching. Eight switches produced no growth.
  • Idle. Eight hours flat to within 5 MB.
  • Surface teardown (Closing terminal tabs leaks Ghostty surfaces and PTY processes #137). Thread trios balanced throughout; closing a pane removes its trio.

Prior history on this machine

0.1.23 hit the same symptom via #137 and was OOM-killed at 21.5 GB anon-rss (journalctl -k, 2026-08-10 22:53). A capture on 2026-08-12 from that build found the identical 282c34ff pattern at 79% of 252 MB sampled. The #138 fix removed the orphaned-surface mechanism but not the framebuffer retention.

Not tested against main

main has moved well past v0.1.24. PR #145 (merged 2026-08-16) upgrades the embedded Ghostty from 81ab8ff to 1a89efa and switches the fork from am-will/ghostty to bvolpato/ghostty — 69 files and roughly +15,400 lines across src/renderer and src/apprt, plus a terminal memory subsystem that does not exist in the released build (src/terminal/mem.zig and src/terminal/compress.zig are absent at 81ab8ff and present at 1a89efa).

Everything above was measured on the released v0.1.24 binary. I have not built or run main. No merge since v0.1.24 mentions memory or buffer lifetime, and the new frame lifetime work (frame_lease.zig, external_frame.zig, CompletionLifetime.zig) is Metal/IOSurface rather than the OpenGL path this report covers, so I would not assume the upgrade addresses this. If you already know it does, say so and I will retest against main rather than take up your time.

Reproduction

Run 0.1.24 with several terminal surfaces under sustained interactive load for a day or more on a large window, then compare VmRSS + VmSwap against uptime. Sample the largest anonymous mapping for the repeating background colour.

Scripts used are attachable: a step watcher that logs new mappings with the focused surface, and the memory sampler described above.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions