8000 Renderer RSS grows ~14 MB/h on 2.42.1 and is never released: 241 -> 575 MB over 23 h in a single renderer process · Issue #385 · amirlehmam/wmux · GitHub
[go: up one dir, main page]

Skip to content

Renderer RSS grows ~14 MB/h on 2.42.1 and is never released: 241 -> 575 MB over 23 h in a single renderer process #385

Closed

Description

@MarcusJenshaug1

What happens

The window's renderer process grows steadily for as long as wmux stays open and
never releases the memory, while every other wmux process stays small.

I first saw this on 2.33.0, updated to 2.42.1 hoping v2.40.0 (#346) had fixed it
as a side effect, and registered the prediction before measuring: under
300 MB after a day would mean fixed, 550-600 MB would mean it survived.

2.42.1 - same renderer process throughout, no restart

uptime renderer RSS
0.03 h 241 MB
0.22 h 174 MB
2.37 h 268 MB
22.74 h 575 MB

14.7 MB/h. It landed inside the predicted "still leaking" band.

2.33.0 - for comparison

uptime renderer RSS
50 h 1048 MB
115 h 3258 MB
126 h 1666 MB (restart in between)

13-20 MB/h. The rate is unchanged between the two versions.

The part that looks diagnostic: closing 8 of 11 workspaces did not free
anything.
RSS went 3258 -> 3382 MB across the closures and kept climbing to
3406 MB. So this is not retained scrollback belonging to live surfaces - the
memory is not reachable from the surfaces that went away.

Expected: RSS roughly proportional to the live surfaces and their scrollback,
and a drop when surfaces are closed.

Steps to reproduce

  1. Open several workspaces with terminal panes and leave long-running agent
    sessions in them (Claude Code here).
  2. Work normally for a day - split panes, zoom, drag tabs between panes.
  3. Watch the largest wmux process:
    Get-Process wmux | Sort-Object WS -Descending | Select-Object -First 1 Id, @{n='MB';e={[int]($_.WS/1MB)}}
  4. Close most of the workspaces and watch the same number.

wmux version

2.42.1 (also reproduced on 2.33.0)

Windows version

Windows 11 Pro, build 22000

Anything else

Possibly related: terminal stutter, and where it is not

Users of this machine see the terminal stutter under load. Measurements that
narrow it down, in case they help:

The main process is healthy. wmux ping has a median latency of 448 ms, but
wmux --help, which never talks to the main process, takes 463 ms. The named
pipe round-trip is therefore effectively zero even with 60 node processes at
60-70 % CPU. The PTY layer and main process are not the bottleneck.

The renderer shows multi-core bursts. Sampling the renderer's
TotalProcessorTime at 50 ms resolution (2-second averages are far too coarse
to see sub-second stalls - a 300 ms full-core stall reads as 0.6 % on 16 cores):

heap 292 MB heap 586 MB
p99 (% of ONE core) 261.7 420.7
max 380.9 480.5

Over 100 % of one core requires several threads at once, which is what parallel
major-GC marking looks like. The peaks grew as the heap grew. Caveat, stated
honestly: the two runs were not under comparable load
(41 vs 24 node
processes), so this is suggestive, not conclusive.

A plain terminal on the same machine under the same load does not stutter.
That rules out simple CPU contention.

Ruled out, so you do not have to

  • Not system memory pressure: commit 57-58 % of 71.9 GB, 9-11 GB available.
  • Not the disk: 97-99 % idle.
  • Not the GPU path: GPUCache and DawnWebGPUCache are both populated.
  • Not WebGL context exhaustion: only 4 surfaces existed.
  • Not transparency: terminalBgOpacity: 88 is gated behind windowTransparency,
    which is off.
  • Not scrollback size: scrollbackLines: 5000.
  • Not Windows Defender: 754 samples over 71 minutes under full load, zero
    samples above 3 %, max 0.0 %.
  • Not renderer process priority: it stayed at Normal through 60 consecutive
    samples, including while another app held the foreground.

Why this report has no heap snapshot, and no patch

The DevTools endpoint on 9222 accepts a connection, but the single page target
there is a stub that never answers Runtime.evaluate - not even 1+1. That may
be related to #157. Without it I cannot name the retaining object, and I would
rather file an accurate observation than guess at a fix.

All 11 releases newer than 2.33.0 were checked: none of their notes mention
memory, a leak, RSS or performance, so this was never knowingly fixed.

Activity

  1. amirlehmam commented on Oct 27, 2026

    @amirlehmam
    Owner

    Thanks for this report, especially the prediction registered before measuring and the observation that closing 8 of 11 workspaces freed nothing. That pointed straight at state kept after a surface goes away.

    v2.46.0 (9d351f2) fixes three retention paths found by auditing what the renderer keeps per surface and per workspace:

    1. Unread notifications grew without limit. The 200 cap evicted only read entries. Every agent Stop in a pane nobody focused, and every notification of a closed workspace (which can never be read by focusing it), stayed forever, and each new one copied the whole array. The cap now also evicts the oldest unread entries, and closing a workspace drops its notifications.
    2. Closed terminal surfaces left per-surface state behind. Mouse modes, wheel/touch remainders, the output counter, parked pane notices, an unused remount snapshot, progress/shell-state records and observer activity were cleared only on PTY exit, which a closed pane never sees. They are now dropped when the surface closes.
    3. A terminal disposed while pty.has() was in flight attached its PTY listeners after cleanup had already run, leaking them and the disposed xterm they hold. That callback now bails out once disposed.

    To be transparent: this came from a code audit, not from a heap snapshot of your workload, so I can't promise it covers all of your 14 MB/h. I'm closing this as fixed in 2.46.0, but your measurement is the real test. If you can run the same protocol on 2.46.0 (same renderer process, ~24 h, plus a before/after on closing workspaces), please post the numbers here and I'll reopen if it is still climbing. A DevTools heap snapshot early and late in the run would help narrow down anything left.

  2. MarcusJenshaug1 commented on Oct 28, 2026

    @MarcusJenshaug1
    Author

    Ran the same protocol on 2.47.0 (so the 2.46.0 fix is in). One renderer
    process, no restart, 24 h, with the load recorded next to every sample.

    Short version: partly better, not fixed, and the way I was measuring it (and
    the way my original report measured it) turns out to be unreliable.

    Something in the fix works: memory now comes back

    28 Oct 15:03   566 MB   22.61 h   3 workspaces, 4 parallel agents
    28 Oct 16:00   635 MB   23.56 h   same, plus an unrelated 6.5 GB build; 6.4 GB free
    28 Oct 16:48   504 MB   24.36 h   load gone, 12 node procs; 11.9 GB free
    

    It gave back 131 MB when the agents stopped and the surfaces went away. That is
    what fix #2 (dropping per-surface state on close rather than on PTY exit)
    should look like, and I never saw it on 2.42.1: there the renderer sat at
    580 MB at 27.96 h after the load dropped and did not move.

    I checked that the drop is real and not Windows trimming the working set:
    WorkingSet 504 MB vs PrivateMemorySize 506 MB at that moment. They match, so
    nothing had been paged out. Peak working set over the run was 680 MB.

    But it does not return to baseline, and it still climbs fast under load

    27 Oct 16:28   253 MB    0.02 h   quiet                      <- baseline
    28 Oct 12:24   344 MB   19.97 h   quiet day, little work     ->  4.6 MB/h
    28 Oct 15:03   566 MB   22.61 h   3 workspaces, 4 agents     -> 84 MB/h
    28 Oct 16:48   504 MB   24.36 h   idle again                 <- 251 MB above baseline
    
    • Idle: 4.6 MB/h. Under four parallel agents: 84 MB/h, roughly 18x.
    • After a full day and with the machine idle again, it is still 251 MB
      above where it started.

    So the growth is driven by agent activity, not by uptime, and a portion of each
    burst is retained permanently.

    The measurement problem, which affects my original report too

    Within a single process, the difference between a loaded sample and an idle one
    was 131 MB. That is larger than the difference between the two versions. A
    single RSS reading is therefore close to meaningless here unless the load at
    that instant is recorded with it.

    Comparing like with like:

    under load idle
    2.42.1 575 MB @ 22.74 h 580 MB @ 27.96 h (never dropped)
    2.47.0 635 MB @ 23.56 h 504 MB @ 24.36 h

    Under load there is no improvement. The improvement is entirely in the release
    behaviour. My original "14.7 MB/h, never released" headline was built on
    loaded samples only, and I should not have stated the rate without the load.

    The notification array is not where the remaining memory is

    Checked directly with wmux list-notifications at 22.6 h and 566 MB:

    69 entries, 0 unread, 3.1 notifications/hour
    

    A 69-element array copied three times an hour costs nothing, so fix #1 is not
    where the residual 251 MB lives. Probably worth not spending more time there.

    (I also thought I had found notifications retained for a closed surface. I had
    not: wmux list-surfaces and list-windows are scoped to the calling pane, so
    the surface I took for dead was alive in another workspace. Retracted.)

    Still no heap snapshot, and precisely why

    The debug port will not allow one, on 2.47.0 exactly as on 2.42.1:

    • Port 9222 listens, owned by the main process.
    • GET /json/version answers: Chrome/150.0.7871.46, V8 15.0.245.13, protocol 1.3
    • GET /json/list returns one target, type: page, id: 1, with empty
      title, url and devtoolsFrontendUrl
    • The WebSocket to ws://localhost:9222/devtools/page/1 connects with no error
    • Then no CDP method ever answers: Runtime.evaluate with 1+1,
      HeapProfiler.enable and Target.getTargets each timed out after 6 s, with
      no response and no error frame.

    It answers discovery but not the protocol. Possibly the same as #157. While
    that holds, nobody can give you a heap snapshot from a real workload, which is
    the one piece of evidence that would localise the remaining 251 MB.

    Second-hand, offered as hearsay only

    Several people I know who use wmux report the same stutter. I have no versions,
    no measurements, and cannot interview them, so this is only weak evidence that
    it is not specific to my machine. Subjectively this version felt better to me,
    but I had a quiet day, and given the load dependence above I would not count
    that as evidence either.

    Environment

    Windows 11 Pro 10.0.22000, 16 logical cores, 32 GB. wmux 2.47.0.
    Chrome 150.0.7871.46, V8 15.0.245.13. Workload: Claude Code agent sessions,
    12 to 24 node processes, 2 to 3 workspaces. Same renderer process (pid 46328)
    for all samples, verified by pid and start time.

    Tell me what you would like measured next and I will run it.

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

      0