Skip to content

fix(core): Free WASM resources and cap MCP caches to prevent memory leaks#1379

Closed
yamadashy wants to merge 1 commit intomainfrom
fix/wasm-memory-leak-and-mcp-cache
Closed

fix(core): Free WASM resources and cap MCP caches to prevent memory leaks#1379
yamadashy wants to merge 1 commit intomainfrom
fix/wasm-memory-leak-and-mcp-cache

Conversation

@yamadashy
Copy link
Copy Markdown
Owner

@yamadashy yamadashy commented Apr 3, 2026

Two independent fixes for memory leaks that affect long-running or heavy usage scenarios:

1. Tree-sitter WASM memory leak (compress/removeComments mode)

parser.parse() returns a Tree object backed by WASM heap memory. Unlike JS objects, WASM allocations are not managed by the garbage collector — they must be explicitly freed via tree.delete(). This call was missing, causing WASM heap to grow with every parsed file.

Similarly, Query objects were not freed in LanguageParser.dispose().

Fix: Added tree.delete() in a finally block in parseFile(), and query.delete() in LanguageParser.dispose().

2. MCP server unbounded cache growth

Two module-level Map caches grow without limit in long-running MCP server processes:

  • outputFileRegistry in mcpToolRuntime.ts — accumulates an entry per pack_codebase / pack_remote_repository call, never evicted
  • fileChangeCountsCache in outputSort.ts — accumulates per unique cwd:maxCommits combination

Fix: Added size caps (100 and 50 respectively) with FIFO eviction using Map's insertion-order iteration.

Checklist

  • Run npm run test
  • Run npm run lint

Open with Devin

…eaks

Two independent fixes for memory leaks in long-running or heavy usage:

Tree-sitter WASM leak: parser.parse() returns a Tree object backed by
WASM heap memory that JS GC cannot reclaim. Added tree.delete() in a
finally block and query.delete() in LanguageParser.dispose().

MCP cache growth: outputFileRegistry and fileChangeCountsCache are
module-level Maps that grow without limit in long-running MCP servers.
Added size caps with FIFO eviction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

⚡ Performance Benchmark

Latest commit:156ed32 fix(core): Free WASM resources and cap MCP caches to prevent memory leaks
Status:✅ Benchmark complete!
Ubuntu:1.57s (±0.03s) → 1.58s (±0.04s) · +0.01s (+0.6%)
macOS:1.09s (±0.12s) → 1.08s (±0.24s) · -0.01s (-0.6%)
Windows:1.86s (±0.06s) → 1.86s (±0.06s) · +0.00s (+0.1%)
Details
  • Packing the repomix repository with node bin/repomix.cjs
  • Warmup: 2 runs (discarded), interleaved execution
  • Measurement: 20 runs / 30 on macOS (median ± IQR)
  • Workflow run

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

The PR introduces memory management improvements by adding fixed upper bounds to two caches/registries with LRU-style eviction (fileChangeCountsCache and outputFileRegistry), and ensuring explicit deletion of Tree-sitter Query and Tree objects during cleanup to prevent memory leaks.

Changes

Cohort / File(s) Summary
Cache and Registry Bounds
src/core/output/outputSort.ts, src/mcp/tools/mcpToolRuntime.ts
Added maximum capacity limits (MAX_CACHE_SIZE = 50 and MAX_REGISTRY_SIZE = 100) with eviction of oldest entries when capacity is reached before inserting new items.
Resource Cleanup
src/core/treeSitter/languageParser.ts, src/core/treeSitter/parseFile.ts
Added explicit deletion of Tree-sitter Query objects in dispose() and wrapped tree parsing with a finally block to guarantee tree.delete() is called after parsing completes or fails.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: freeing WASM resources and capping MCP caches to prevent memory leaks, which matches all four file modifications.
Description check ✅ Passed The description fully addresses the repository template, includes comprehensive explanations of both fixes with context and rationale, and confirms both checklist items (npm run test and npm run lint) were completed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/wasm-memory-leak-and-mcp-cache

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses potential memory leaks and unbounded resource growth in long-running processes. Key changes include implementing FIFO eviction strategies for caches in outputSort.ts and mcpToolRuntime.ts, and ensuring native WASM memory is properly released in tree-sitter components by calling .delete() on queries and trees. Review feedback highlights that gitAvailabilityCache should also be capped to prevent memory growth and suggests implementing disk cleanup for temporary files when they are evicted from the output registry.

// Capped to prevent unbounded growth in long-running MCP server processes.
// Key format: `${cwd}:${maxCommits}`
const MAX_CACHE_SIZE = 50;
const fileChangeCountsCache = new Map<string, Record<string, number>>();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The gitAvailabilityCache (declared at line 15) is also a module-level Map that grows without limit as different directories are processed. To fully address the memory leak concerns in long-running processes, this cache should also be capped using a similar FIFO eviction strategy as fileChangeCountsCache. This is particularly relevant because cwd changes for every remote repository packing operation.

Comment on lines +16 to +21
if (outputFileRegistry.size >= MAX_REGISTRY_SIZE) {
const oldestKey = outputFileRegistry.keys().next().value;
if (oldestKey !== undefined) {
outputFileRegistry.delete(oldestKey);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

While evicting entries from outputFileRegistry prevents memory growth, the corresponding temporary files on disk are not deleted. In long-running scenarios with heavy usage, this could lead to significant disk space consumption in the system's temporary directory. Consider implementing a best-effort cleanup of the file when an entry is evicted (e.g., using fs.unlink). Note that registerOutputFile is currently synchronous, so this would need to be handled carefully to avoid blocking or complex async logic.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 3, 2026

Codecov Report

❌ Patch coverage is 44.44444% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.24%. Comparing base (a579381) to head (156ed32).

Files with missing lines Patch % Lines
src/core/output/outputSort.ts 40.00% 3 Missing ⚠️
src/mcp/tools/mcpToolRuntime.ts 40.00% 3 Missing ⚠️
src/core/treeSitter/languageParser.ts 0.00% 2 Missing ⚠️
src/core/treeSitter/parseFile.ts 66.66% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1379      +/-   ##
==========================================
- Coverage   87.37%   87.24%   -0.13%     
==========================================
  Files         115      115              
  Lines        4371     4383      +12     
  Branches     1015     1019       +4     
==========================================
+ Hits         3819     3824       +5     
- Misses        552      559       +7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/core/output/outputSort.ts (1)

14-15: Consider bounding gitAvailabilityCache for consistency.

While fileChangeCountsCache is now bounded, gitAvailabilityCache (line 15) remains unbounded. In long-running MCP server processes with many distinct cwd values, this could also grow without limit.

The impact is likely minimal since it only stores boolean values, but for consistency with the PR's goal of preventing unbounded cache growth, you might consider applying the same eviction pattern here.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/output/outputSort.ts` around lines 14 - 15, gitAvailabilityCache is
currently an unbounded Map while fileChangeCountsCache was changed to a bounded
LRU-style cache; make gitAvailabilityCache use the same bounded eviction
strategy to prevent unbounded growth. Replace the Map<string, boolean>
gitAvailabilityCache with the same bounded cache implementation used for
fileChangeCountsCache (reuse the same MAX_CACHE_ENTRIES constant and eviction
logic or helper class), ensure lookups/sets use the new cache API, and keep the
key as cwd and value as boolean so all existing call sites (references to
gitAvailabilityCache) continue to work.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/core/output/outputSort.ts`:
- Around line 14-15: gitAvailabilityCache is currently an unbounded Map while
fileChangeCountsCache was changed to a bounded LRU-style cache; make
gitAvailabilityCache use the same bounded eviction strategy to prevent unbounded
growth. Replace the Map<string, boolean> gitAvailabilityCache with the same
bounded cache implementation used for fileChangeCountsCache (reuse the same
MAX_CACHE_ENTRIES constant and eviction logic or helper class), ensure
lookups/sets use the new cache API, and keep the key as cwd and value as boolean
so all existing call sites (references to gitAvailabilityCache) continue to
work.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0a08b6b7-5abe-4b97-bcdf-bee084b6476f

📥 Commits

Reviewing files that changed from the base of the PR and between a579381 and 156ed32.

📒 Files selected for processing (4)
  • src/core/output/outputSort.ts
  • src/core/treeSitter/languageParser.ts
  • src/core/treeSitter/parseFile.ts
  • src/mcp/tools/mcpToolRuntime.ts

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Apr 3, 2026

Code Review

Overall this is a clean, well-motivated PR. The WASM resource cleanup is correct and the cache caps are a sensible safeguard for long-running MCP servers. Approve with minor suggestions.

Highlights

  • WASM cleanup is correct. tree.delete() in finally ensures cleanup on both success and error paths. query.delete() before parser.delete() in dispose() is the right ordering. The early return for null tree avoids the finally block entirely — well done.
  • FIFO eviction is appropriate. Map.keys().next().value is O(1) in V8 and leverages insertion-order semantics cleanly. For both caches, older entries are naturally less relevant than newer ones, making FIFO a good fit (no need for LRU complexity).
  • Evicted outputFileRegistry entries are handled gracefully. Both callers (readRepomixOutputTool.ts and grepRepomixOutputTool.ts) check for undefined and return proper error responses.

Suggestions

1. Missing tests for eviction behavior (recommended)

The FIFO eviction logic is the core new behavior in this PR but has no test coverage. Two tests would be straightforward:

  • mcpToolRuntime.test.ts (easiest): Register 101 entries, verify the first is evicted (getOutputFilePath returns undefined).
  • outputSort.test.ts: Call sortOutputFiles with 51 distinct configs, verify the first cache entry triggers a fresh getFileChangeCount call.

These would catch regressions if the eviction logic is ever refactored.

2. mcpToolRuntime.ts is at 257 lines (nit)

The project guideline is 250 lines per file. The registry logic (registerOutputFile, getOutputFilePath, MAX_REGISTRY_SIZE, outputFileRegistry) is a self-contained concern that could be extracted. Low priority — the file is only slightly over the limit.

LGTM — nice fix for a real issue affecting long-running MCP servers. 🛠️

Copy link
Copy Markdown
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

@yamadashy
Copy link
Copy Markdown
Owner Author

Closing — no measurable memory improvement in benchmarks (compress mode 300-iteration test showed identical RSS/heap between this branch and main). The WASM trees for typical source files are too small to produce visible impact, and MCP cache growth is negligible in practice.

@yamadashy yamadashy closed this Apr 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant