find::fs_cache — the shared scan cache
fs_cache is a process-global, TTL-based cache of scanned directory entries.
Both glob and grep use it when you pass
cache: true: a second search of the same directory with the same visibility
settings reuses the earlier scan instead of re-walking the tree.
It exists because directory scans are the expensive part of discovery — the
matching itself is cheap. Caching the scan amortizes the walk across many
queries (think: an agent running a dozen find_glob calls against the same
project in a session).
Policy
| Setting | Default | Env override |
|---|---|---|
| Cache TTL | 1000 ms |
FS_SCAN_CACHE_TTL_MS |
| Empty-result recheck age | 200 ms |
FS_SCAN_EMPTY_RECHECK_MS |
| Max cache entries | 16 |
FS_SCAN_CACHE_MAX_ENTRIES |
| Walker threads | 4 |
PI_GREP_WORKERS |
FS_SCAN_CACHE_TTL_MS = 0disables caching entirely (always fresh scans).- The cache is keyed by (root,
include_hidden,use_gitignore,skip_node_modules,ScanDetail) — a scan with different visibility settings is a different cache entry. - The oldest entry is evicted when the cache exceeds
max_cache_entries. - Partial (timed-out) scans are never cached — a snapshot that was cut short must not be served later as a complete scan.
The three cache operations
pub fn get_or_scan(root: &Path, options: ScanOptions, ct: &CancelToken) -> Result<ScanResult, String>
pub fn force_rescan(root: &Path, options: ScanOptions, store: bool, ct: &CancelToken) -> Result<(Vec<GlobMatch>, bool), String>
pub fn invalidate_path(target: &Path) // drop entries whose root is a prefix of target
pub fn invalidate_all() // clear everything
get_or_scan— serve from cache if fresh, otherwise scan and store. The returnedScanResultcarriescache_age_ms(how stale the served data was) andtimed_out.force_rescan— always scan fresh;store: falsereturns the result without repopulating the cache.invalidate_path/invalidate_all— call these after mutating files so subsequent searches see the new state. This is exactly what thecosh-toolswrappers do after everyfs_edit.
The empty-result fast recheck
A cached negative can be stale: a file created 50 ms ago is invisible to a
scan cached 900 ms ago. So glob and grep implement a fast recheck: if
a cached scan yields zero matches and the cache is older than
empty_recheck_ms(), they force_rescan and try once more before returning
empty. This keeps “create file, then immediately search for it” flows working
without paying for a fresh scan on every query.
ScanDetail — how much metadata
| Detail | Collects |
|---|---|
Minimal |
path + file_type only (mtime/size are None) |
Full |
path + file_type + mtime (ms) + size |
glob uses Full only when sort_by_mtime is on (it needs mtimes to rank);
otherwise it scans Minimal. grep always scans Minimal — it only needs
the file list.
Types
pub struct GlobMatch {
pub path: String, // relative to the scan root, forward slashes
pub file_type: FileType, // File | Dir | Symlink
pub mtime: Option<f64>, // millis since Unix epoch
pub size: Option<f64>, // bytes, files only
}
pub enum ScanOptions { include_hidden, use_gitignore, skip_node_modules, follow_links, detail }
pub struct ScanResult { entries: Vec<GlobMatch>, cache_age_ms: u64, timed_out: bool }
Next: task — cancellation.