[Bug]: fff 0.9.4 watcher keeps rescanning large non-git workspaces and holds the backend at 60–190% CPU
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
- #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 を使用し、upstream のバイナリ検出変更を追加せずに、再スキャン頻度と 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
- 平均マージ
- 6時間 23分
- マージ済み PR(30日)
- 203
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
pingdotgg/t3code のほかの issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 68/100
メンテナーはふだん 1 日以内に返信
-
accepted bug via-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
pingdotgg/t3code#19536 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
メンテナーはふだん 1 日以内に返信
-
[Bug]: Diff panel hides the current branch name and the arrow overlaps the base ref when the panel is narrow対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープンaccepted bug via-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
pingdotgg/t3code#19048 · コメント 1 件 ·
メンテナーはふだん 1 日以内に返信
-
[Bug] Windows repository cloning fails while creating an existing drive root対応中かも @pujitha24 が 4 日前に担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 73/100
メンテナーはふだん 1 日以内に返信
pingdotgg/t3code の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 74/100
xdjs/MusicNerdWeb#1532 ·
メンテナーはふだん 1 日以内に返信
-
bug
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
sebastian-software/ardo#352 ·
メンテナーはふだん 1 日以内に返信
-
@react95/core good first issue
難易度 1/5 1時間未満 初心者へのやさしさ 88/100
メンテナーはふだん 3 日以内に返信
-
@shikijs/stream: `ShikiStreamTokenizer.clone()` shares its `tokensStable` array with the original対応中かも @xyrolle が今日担当しました。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 82/100
メンテナーはふだん 6 日以内に返信
-
難易度 2/5 1〜3時間 初心者へのやさしさ 72/100
メンテナーはふだん 1 日以内に返信