Skip to content

fff-mcp: cache-limit miswiring and unbudgeted index allocation on large trees (0.9.6 review) #847

Description

@mgpai22

Summary

On Linux, two unscoped fff-mcp processes launched from a broad non-Git temporary directory reached approximately 8–11 GiB RSS each and consumed multiple CPU cores. The host suffered severe memory pressure. Stopping the oversized indexers relieved memory pressure.

A source review found concrete resource-control problems in the installed version. This report does not claim that a permanent memory leak has been demonstrated, or that one of these defects accounts for the entire observed RSS. The indexed file count and allocation profile were not captured before containment.

Environment

  • Linux x86_64, 20 logical CPUs, approximately 86 GiB RAM
  • Installed binary: fff-mcp 0.9.6 (28321da22836b0e11da81e30f40f7a043b8f8fb4)
  • MCP stdio integration, no server arguments and no FFF_* overrides
  • Affected indexers had cwd /tmp
  • Relevant source also compared with main at d84c0a10cd5ea23285cb5575fa90179f51710f99; this is not a claim about an exact released version

1. --max-cached-files is interpreted as repository size, not a cap

The option promises a maximum number of persistently cached files, but its value is passed to ContentCacheBudget::new_for_repo:

For example, --max-cached-files 2000 constructs a budget with 30,000 files and 512 MiB, rather than a 2,000-file cap. The environment variable takes the same path. The separate byte budget still applies; this does not imply 30,000 unrestricted mappings.

Expected: apply the supplied maximum directly, with explicit semantics for zero. The same mapping remains on inspected main.

This is a source-derived deterministic counterexample, not a claim that a workload reproduction has been run. The affected processes used defaults, so this particular option bug did not cause their original growth; it prevents a reliable mitigation.

2. Content-index construction is outside the content-cache memory budget

build_bigram_index creates two builders sized by the full indexable file count:

When both slabs materialize, their combined storage is:

2 * 5000 * ceil(indexable_file_count / 64) * 8 bytes

That is approximately 1,250 bytes per indexable file, before additional compression/output allocations. One million indexable files means approximately 1.16 GiB for these slabs alone. That is a scale example, not the measured file count of the affected directory.

The content-cache byte budget does not cover these builders. Content indexing is enabled by default when warmup is enabled: MCP defaults.

Expected: estimate builder memory before allocation and skip or use a bounded alternative when an explicit budget would be exceeded. Basic searches should remain available. The dense two-builder design also remains on inspected main.

3. Concurrent cache admission can overshoot and miscount

Cache admission checks counters, creates a mapping, and increments counters afterward.

  • Different concurrent file accesses can pass the same remaining-budget check.
  • Concurrent accesses to one cold FileItem can each increment the counters although OnceLock::get_or_init retains only one mapping.
  • Invalidation subtracts only the stored mapping's contribution.

Expected: atomic budget reservation with rollback, and accounting only for the successful insertion. This can cause bounded concurrent overshoot and inflated accounting; it is not evidence of a permanent mapping leak.

4. Version 0.9.6 rescans repeat directory indexing through an unbounded queue

After a full rescan, resubscription queues every indexed directory. The watcher owner then calls track_files_from_new_directories, even though the scan already indexed those directories. Its mpsc::channel has no capacity bound.

This produces another directory traversal and can retain a backlog if resubscription outpaces the consumer. It is a repeated-work defect, not proof that this queue alone explains multi-GiB RSS.

Inspected main separates WatchTask::Subscribe from IndexNewDir, which addresses the redundant indexing: new watcher. This item is included as version-specific context, not a claim that the old behavior persists on main.

Related: #690 and #616 discuss other rescan triggers. This report focuses on memory budgeting, cache admission, and redundant work after a rescan, rather than duplicating those trigger reports.

Scope and mitigation

FFF's fallback to the current non-Git directory is documented behavior, not itself claimed as a bug. The concern is the absence or incorrect enforcement of resource controls once that directory is large.

We disabled FFF for broad temporary-directory sessions while preserving repository-scoped instances. No upgrade or heavy reproduction was attempted on the overloaded host. A default log-file feedback loop was not established, and the coding-agent parent's separate memory/CPU use is not attributed to FFF here.

Could the explicit cache-limit bug and a pre-allocation content-index budget be addressed first? I am happy for maintainers to split these source findings into separate issues if preferred.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions