[Bug]: fff 0.9.4 watcher keeps rescanning large non-git workspaces and holds the backend at 60–190% CPU
维护者通常 1 天内回复
还没有人认领这个 Issue。
- #14537 来自 @realhasanshoaib —— 已关闭,未合并
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 68/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- nodejs, typescript
- 领域
- backend, performance
调研方向
从 apps/server 开始,找到 @ff-labs/fff-node 的 pin,并检查 issue 中的版本比较和 bench.cjs harness。使用该 harness 或等效的复现方式,在大型非 Git workspace 上验证依赖项更新。完成的标准是项目使用 fff-node 0.11.0,且重新扫描频率和 CPU 使用率得到改善,同时不添加上游的二进制嗅探变更。
由索引模型根据 Issue 内容生成。
描述
Before submitting
- I searched existing issues and did not find a duplicate.
- I included enough detail to reproduce or investigate the problem.
Area
apps/server
Steps to reproduce
- On macOS, add a large non-git folder as a project. Mine is
~/code: the parent folder of about 170 repos, about 1.65M files in total. fff indexes about 120k–225k of them after its built-in skips. - Keep agents working in repos under that folder, so files change all the time: builds, installs, logs and scratch output.
- Use anything that builds the path index for that project once (the
@picker, the Files panel or a file link). Then leave the app open.
Expected behavior
After its first scan, the workspace index follows changes cheaply. An idle app does not keep 1–2 cores busy.
Actual behavior
The desktop main process (which hosts the backend) stays at 60–190% CPU with nothing running. sample shows the time inside fff's native threads, not in JS. The Node main thread is idle in kevent in 5151 of 5922 samples.
fff-scan (busy in 100% of samples):
fff_search::scan::ScanJob::run
-> bigram_filter::sniff_binary_for_non_indexable
-> FileItem::detect_binary_per_byte -> read() 4229
-> FileItem::detect_binary_per_byte -> open() 1549
fff-watcher-own (busy in ~50% of samples):
FilePicker::handle_file_modify
-> FileItem::detect_binary_per_byte -> open() 1308
-> FileItem::detect_binary_per_byte -> read() 1252
A new fff-scan thread appears about every 35–40 s, so fff keeps rescanning the whole tree. server.trace.ndjson shows only two WorkspaceSearchIndex.make spans in that window, each about 2.8 s and successful. So T3 is not rebuilding the index. The rescans come from inside fff.
Cause
T3 pins @ff-labs/fff-node 0.9.4. Its watcher (crates/fff-core/src/background_watcher.rs) starts a full rescan whenever any of these happens:
- a debounced batch has more than 1024 affected paths (
MAX_OVERFLOW_FILES), counted before ignore filtering - more than 1024 new files are waiting in the overflow list
- FSEvents sets its rescan flag
- any
.gitignoreor.ignorechanges anywhere under the root
None of these are rate-limited. Under a folder that holds many active repos, one of them fires almost all the time.
fff fixed this upstream in dmtrKovalenko/fff#751, "Reduce amount of rescans in giant /Users/neogoose like folders" (commit 031005e). It adds a RescanThrottle: watcher rescans wait at least 30 s, and 5 minutes for non-git roots with 1M or more files. The first stable release with the throttle is 0.10.3. The current release is 0.11.0.
A second cost remains in 0.11.0 and on fff main. FileItem::detect_binary_per_byte reads every new or changed file to EOF, and it keeps reading after it has found a NUL byte. On the watcher path (handle_create_or_modify, add_new_file) there is no size cap. So each append to a log, sqlite file or media file under the root re-reads the whole file. That belongs upstream in fff.
Measurements
I ran a standalone harness with the exact options T3 passes for the paths index (disableMmapCache, disableContentIndexing, aiMode: false, enableFsRootScanning, enableHomeDirScanning). It ran on the same ~/code with real agent activity. Every 20 s it also writes and deletes 1500 files under a node_modules folder. Each version ran for about 4 minutes, one after the other, on an Apple M4.
| fff-node | full scans | process CPU | average |
|---|---|---|---|
| 0.9.4 (shipped) | 7 in 240 s | 191 s | 80% of a core |
| 0.11.0 | 4 in 291 s | 137 s | 47% of a core |
The bump removes about 40% of the CPU and about half of the rescans. Most of what's left is the per-file sniff described above.
Harness (node bench.cjs <dir with fff-node installed> <root> <seconds>)
const fs = require("node:fs");
const path = require("node:path");
const [fffDir, root, seconds] = process.argv.slice(2);
const { FileFinder } = require(path.resolve(fffDir, "node_modules/@ff-labs/fff-node"));
const finder = FileFinder.create({
basePath: root, disableMmapCache: true, disableContentIndexing: true,
aiMode: false, enableFsRootScanning: true, enableHomeDirScanning: true,
}).value;
const churnDir = path.join(root, ".fffbench-churn", "node_modules", "pkg");
const cpuStart = process.cpuUsage();
let scanning = finder.isScanning(), scans = scanning ? 1 : 0;
setInterval(() => { const now = finder.isScanning(); if (now && !scanning) scans++; scanning = now; }, 100);
setInterval(() => {
fs.mkdirSync(churnDir, { recursive: true });
for (let i = 0; i < 1500; i++) fs.writeFileSync(path.join(churnDir, `f${i}.js`), "x");
for (let i = 0; i < 1500; i++) fs.unlinkSync(path.join(churnDir, `f${i}.js`));
}, 20_000);
setTimeout(() => {
const cpu = process.cpuUsage(cpuStart);
console.log({ scans, cpuSeconds: (cpu.user + cpu.system) / 1e6 });
process.exit(0);
}, Number(seconds) * 1000);
Suggested fixes
- Bump
@ff-labs/fff-nodeto 0.11.0 to get the rescan throttle. I'll open a PR that links this issue. - Upstream in fff: stop
detect_binary_per_byteat the first NUL byte, and cap or skip large files on the watcher path, as the bulk scan already does withMAX_FFFILE_SIZE. - In T3, consider not keeping a live watcher on very large non-git roots, or on a root that contains other registered projects.
Related
- #11306: the same fff threads block the event loop on a home-directory project.
- #10917: the 15 s index scan timeout.
- #10918: no way to exclude directories outside git.
- #11500: keeps a partial index on timeout. That PR is complementary and doesn't touch the watcher.
Impact
Major degradation or frequent failure
Version or commit
T3 Code (Nightly) 0.0.45-nightly.20260930.2493. Code is current main (8630e1ac7).
Environment
macOS 26.6.2 (25G83), MacBook Air M4 (Mac16,12), 16 GB RAM, local APFS disk.
Workaround
Avoid adding a large non-git parent folder as a project, or avoid opening anything that indexes it. The index closes 15 minutes after its last use.
- 主要语言
- TypeScript
- 星标
- 24.8k
- 派生
- 6.4k
- 平均合并
- 4 小时 17 分钟
- 30 天内合并 PR
- 487
环境准备
在浏览器里用你自己的 GitHub 账号启动这个项目的开发容器。
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
pingdotgg/t3code 的其他 Issue
-
难度 2/5 1-3 小时 新手友好度 78/100
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 77/100
pingdotgg/t3code#21107 · 1 条评论 ·
维护者通常 1 天内回复
-
[Bug]: ManagedOOMPreference=avoid in t3code.service puts systemd --user into a 100% CPU loop with uresourced (Fedora/GNOME)可能已有人在做 @nateEc 于 2 天前认领。 未关闭accepted bug via-triage
难度 2/5 1-3 小时 新手友好度 69/100
pingdotgg/t3code#20931 · 1 条评论 ·
维护者通常 1 天内回复
-
[Bug]: Every test run leaves a temp folder behind (vitest 5.0.1 via vite-plus 1.0.0)可能已有人在做 @Lenoridev 今天认领。 未关闭bug via-triage
难度 2/5 1-3 小时 新手友好度 72/100
pingdotgg/t3code#20128 · 1 条评论 ·
维护者通常 1 天内回复
-
[Bug]: T3_CODE_ORCHESTRATION_INSTRUCTIONS has no carve-out for user- or repo-mandated CLI review workflows (unlike T3_CODE_BROWSER_TOOL_INSTRUCTIONS)可能已有人在做 @nivethass15102007-tech 于 4 天前认领。 未关闭
难度 2/5 1-3 小时 新手友好度 68/100
pingdotgg/t3code#19908 · 1 条评论 ·
维护者通常 1 天内回复
相似的 Issue
-
docs(api): openapi.yaml still documents gif-tools optimize colors as 2-256 instead of 2, 4, 16, 256未关闭area: backend documentation priority: low
难度 2/5 1-3 小时 新手友好度 84/100
snapotter-hq/SnapOtter#2919 ·
维护者通常 1 天内回复
-
accepted :+1: bug db/redis
难度 2/5 1-3 小时 新手友好度 76/100
beekeeper-studio/beekeeper-studio#4882 · 1 条评论 ·
维护者通常 1 天内回复
-
ready-for-triage
难度 2/5 1-3 小时 新手友好度 72/100
konflux-ci/konflux-ui#1630 · 1 条评论 ·
维护者通常 2 天内回复
-
难度 2/5 1-3 小时 新手友好度 67/100
ahmedkhaleel2004/gitdiagram#233 ·
-
难度 2/5 1-3 小时 新手友好度 72/100
simkimsia/netlify-axi#19 ·
维护者通常 1 天内回复