Skip to content

GC never collects when idle: heap freezes at last-allocation state — cc TUI pins 425-680 MB indefinitely; pacing+defrag defaults leave ~200 MB on the table #9589

Description

@proggeramlug

Measured 2026-09-03 on the verified cc build (macOS, main 8d27f7b964), three-arm PTY experiment (idle → 400 KB paste-churn → 4-min settle), footprint sampled every 10 s. Raw data /tmp/gc_arms.csv on the dev box.

arm idle sawtooth trough post-churn (4 min)
default 449 MB 425 MB, flat — zero collections
MAJOR_PACING_GROWTH=1 FLOOR_MB=8 ~365 MB 564 MB, flat
+ OLD_DEFRAG=1 ~365 MB 458 MB, flat

Finding 1 — no idle-time collection (the structural gap)

Collections trigger on allocation. An idle TUI barely allocates ⇒ after a burst, the heap freezes at whatever the last mid-burst cycle left — the post-churn column is last-collection timing luck (564 > 425 under more aggressive pacing proves it), and it persists indefinitely. A live user session sat pinned at 680 MB through minutes of idle for exactly this reason. Node idles the same bundle at ~150–200 MB because V8 runs an idle memory-reducer; perry has no equivalent. Fix shape: an idle hook (the event loop knows it is parking — the #9540 body_check site is where "about to idle" is known) that after N quiet seconds runs a major + page return, budgeted so a keystroke never lands mid-cycle (incremental or bounded pause).

Finding 2 — default pacing + defrag leave ~200 MB combined

Aggressive pacing lowers the idle trough 449→365 (~85 MB). OLD_DEFRAG=1 reclaimed ~105 MB of post-burst residue. Both knobs work today; the defaults are tuned for throughput benchmarks, not resident TUIs. Proposal: idle-reducer runs with the aggressive profile (collect-to-live + defrag) regardless of the throughput defaults, since by definition nobody is waiting.

Finding 3 — bounded by a separate census

Fresh-idle live set is ~330 MB GC (vs node's whole process 150–200) — that remainder is representation/side-tables territory, being censused separately; this issue is only the two collector-behaviour gaps above.

Verification bar

The three-arm PTY harness is the regression test shape: post-churn line must descend within N idle seconds instead of staying flat, without keystroke-latency regression (assert input-echo timing under the reducer). The 2.1 GB transient peak during heavy ops should also shrink under pacing review — assert peak in the harness too.

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions