Skip to content

Webview renderer leaks ~25 MB/hour while idle #483

Description

@nedtwigg

Summary

The Dormouse webview renderer leaks roughly 25 MB/hour on a completely idle terminal. Growth tracks wall-clock time, not terminal output. Measured over a 9.6-hour idle window the webview grew 73 MB → 322 MB (4.4x) while only 2.5 MB of PTY output flowed through the whole session.

The pty-host process is unaffected and behaves well (see "What is not leaking" below).

Evidence

Sampled PSS every 30s from /proc/<pid>/smaps_rollup, with terminal output volume taken from the pty-host's /proc/<pid>/io rchar counter as an activity proxy.

Overnight run, machine idle:

23:30    67 MB      04:00   187 MB
00:30    94         06:00   240
01:30   120         07:30   279
02:30   146         08:30   306
03:30   173         09:03   322   <- peak

These are 30-minute minima, not means. The floor rose monotonically across all nineteen buckets with no reversal — memory is never reclaimed. A sawtooth (cache/scrollback churn) would show a flat floor with oscillating peaks.

Reproduced across three separate renderer processes, including two spanning an active workday of builds and one spanning a dead-quiet night:

instance duration range rate
A 1.0 h (active) 77.6 → 108.2 MB +19.7 MB/h
B 7.3 h (active) 70.1 → 273.0 MB +27.0 MB/h
C 9.6 h (idle) 70.4 → 322.3 MB +25.1 MB/h

Near-identical slope under a full workday of terminal activity and under an idle night is the key result: output volume is not the driver.

Control: the VS Code main window renderer, same Electron, same night, same host: +6.55 MB/h — ~4x slower. So this is not baseline Electron drift.

Each instance starts around 70 MB, so the leak appears to begin at webview creation.

What is not leaking

The pty-host process is a clean bill of health, worth recording so it can be ruled out:

  • +0.49 MB/hour over a 7.3-hour continuous lifetime, and it reclaims — hourly means rose 27.8 → 34.9 MB then fell back to 32.4 MB and held. Bounded sawtooth.
  • Process lifecycle is clean: exactly one pty-host at a time, no orphans across several extension-host restarts, and child count tracked open panes with no churn or accumulation.

Impact

At 25 MB/h the webview passes ~600 MB in a day and ~1 GB in under two, with nobody typing.

This compounds a second issue on memory-constrained machines: processes spawned from a Dormouse terminal inherit VS Code's cgroup (app-code-*.scope), so on a systemd-oomd system the leak is charged to the editor's scope and counts toward the kill decision. On a 6.6 GB laptop this contributed to systemd-oomd killing the whole VS Code scope. An editor left open over a weekend could reach the threshold from the leak alone.

Suspects

Idle growth on a clock points away from scrollback and toward something on a timer. Ranked by fit, unverified:

  1. lib/src/lib/quiesce-detector.ts / lib/src/lib/alert-manager.ts — the Activity layer is timer-driven by design (docs/specs/alert.md); an interval appending without eviction matches this signature.
  2. lib/src/lib/terminal-state-store.ts — command runs / title candidates, if they accrue per tick rather than per command.
  3. lib/src/lib/session-dirty.ts — its own comment notes state changes "even with no user activity", which is exactly the observed condition.

Reproduction

  1. Open a Dormouse panel in VS Code with one idle terminal. Do not type.
  2. Sample the webview renderer's PSS every 30s:
    awk '/^Pss:/{s+=$2} END{print s}' /proc/<renderer-pid>/smaps_rollup
    The webview is the VS Code renderer starting at ~70 MB (the main window renderer starts at ~200 MB+).
  3. Growth of ~25 MB/hour appears within an hour and stays linear.

Fastest path to a culprit: Developer: Open Webview Developer Tools, take heap snapshots an hour apart and compare retained sizes. At this rate the delta should name the retaining object directly.

Environment

  • Dormouse extension 1.1.0
  • VS Code 1.135.0 (Electron 42.8.1), Wayland
  • Ubuntu 26.04.1 LTS, kernel 7.0.0-30-generic, x64
  • 6.6 GB RAM, systemd-oomd active

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions