Skip to content

memory: charge or release retained parse trees, regex caches, and fixed runtime heap #2525

Description

@ScriptedAlchemy

Summary

Follow-up to #2490. Retained-heap attribution of an isolated daemon shows which live Rust/C heap no resident owner charges. #2490's refresh starvation and the missing swap term are fixed separately; the components below are still uncharged and not released by any owner.

Evidence

Isolated daemon (debug tracedecay/production build with the system allocator so heaptrack sees every allocation), MemoryMax=6G, MemorySwapMax=1G, isolated HOME/TRACEDECAY_DATA_DIR. Corpus: depth-1 clone of master at cc5a895 (6,684 files). The snapshot was taken after the cold index sealed generation 1 and one refresh commit had been attempted (and refused on master). heaptrack 1.5 --raw; live bytes are counted at the kill point, grouped by call site.

Component (live at snapshot) Bytes Allocations Owner today
Privacy credential regex lazy-DFA caches (one per regex per scanning thread) 201,597,261 201,330 none
Tree-sitter trees in the per-worktree SharedRetainedParsePool (256 docs, bound is on source bytes only) 181,191,207 1,868,404 none
Tool / MCP / SDK catalog and schema caches (process-lifetime) 130,039,371 996,287 none (fixed)
Captured source byte pool (SharedCodeIndexBytePoolV1) 101,735,806 51,887 ledger (snapshot charge)
Canonical serializer / extraction rows 29,945,854 4,305 none
SQLite page cache, schema, statements 21,142,019 126,815 none
Sealed manifest / retained text generation 12,074,505 119,590 none
Other 2,584,369 16,829
Total live heap 680,384,120 3,385,448

At that point the same process had RssAnon 2,423,068 kB plus VmSwap 1,037,368 kB, and no resident owner was charged. heaptrack's own trace tree (27.1M nodes) inflates the RSS, so the gap between RSS and live heap there is mostly heaptrack plus allocator retention. It is not live Rust state.

Production (mimalloc) debug build from the fix branch, same corpus and cap, cold index plus one admitted refresh (generation 2 sealed at the refresh commit): RssAnon 3,306,024 kB, VmSwap 1,048,448 kB, admission sample 4,483,579,904 B. Charged owners were decoded_generation 2,760,435,008 B, which leaves ≈1.6 GB above the charged owners.

What remains

  1. Retained parse trees: per worktree, bounded by document count, not charged. There is no truthful byte measure for tree-sitter trees today. Either (a) register the pool as a resident owner with a structural tree-bytes bound (node count × subtree layout) and release it idle or under pressure, or (b) stop retaining trees parsed by a cold full build. Only changed-file rebuilds benefit from retention.
  2. Regex caches: bounded by rules × concurrent scanners × the lazy-DFA cache capacity (2 MiB default), never released. Needs a measured decision: a bounded cache capacity or a shared scanner width. Hotpath before/after on the privacy scan is required before changing it.
  3. Fixed runtime allowance: the catalog caches (130 MB), SQLite, and allocator overhead need an allowance defined by the resident-memory authority. Then the charged-owners-plus-allowance invariant can be asserted against measured anon RSS.
  4. Graph activation under load: in the debug build on a loaded host, graph publication probe deadline exceeded retries forever while the 2.76 GB decoded generation stays held for the retry. code_graph_serving stays pending.

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