Skip to content

detect_binary_per_byte reads every changed file to EOF (no early exit, no size cap on watcher path) #895

Description

@realhasanshoaib

Which fff frontend?

Node SDK (@ff-labs/fff-node). The code is in fff-core, so it affects every frontend.

Has logs

Stack samples are below. I didn't enable the fff log file for this.

Description

FileItem::detect_binary_per_byte (crates/fff-core/src/types.rs, unchanged on main at 89c1927) reads the whole file to EOF. It has two problems:

  1. No early exit. When a chunk contains a NUL byte, it calls set_binary(true) and keeps reading until EOF. One NUL byte is enough to decide.
  2. No size cap on the watcher path. The bulk scan (bigram_filter.rs) skips files above MAX_FFFILE_SIZE. But handle_create_or_modify and add_new_file call detect_binary_per_byte on every created or modified file of any size. So each write to a large text file re-reads the whole file: think of an append-only log, a JSONL trace or a large generated file under the root.

In practice this is the main CPU cost when the root is a large folder that changes a lot. T3 Code embeds fff for its workspace index. With a ~1.65M-file non-git parent folder of active repos (macOS, Apple M4), sample of the host process showed:

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

fff-scan (busy in 100% of samples):
  ScanJob::run -> bigram_filter::sniff_binary_for_non_indexable
    -> FileItem::detect_binary_per_byte -> read()   4229
    -> FileItem::detect_binary_per_byte -> open()   1549

I ran a standalone fff-node harness on that folder with T3's options (disableContentIndexing: true, disableMmapCache: true). Upgrading 0.9.4 → 0.11.0 brought the process from 80% to 47% of a core, thanks to the #751 rescan throttle, which is great. In both versions, most of the remaining CPU is this sniff, not the scans: 7 scans of about 2–4 s each took 191 s of CPU in 0.9.4.

Suggested fix (a few lines):

Ok(n) => {
    if detect_binary_content(&chunk[..n]) {
        self.set_binary(true);
        break;
    }
}

Also cap the bytes read: either skip files above MAX_FFFILE_SIZE on the watcher path, as the scan already does, or read only the first N KB as git does (it checks the first 8000 bytes). The second option changes classification for text files that have a NUL byte late in the file. A cap on the watcher path alone keeps today's results for anything the scan already classifies.

Downstream report: pingdotgg/t3code#14536

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