pub struct MtimeFileCache<T> { /* private fields */ }Expand description
Per-path memoization of a file’s parsed contents, invalidated by mtime.
Caches whatever parse produces for a file’s raw content — validation, expansion, and
policy checks are expected to run per call on the returned value, never cached. Absence
is never cached: only a file that existed, was a regular file, and parsed successfully
gets an entry, so a file created after the cache first found nothing is picked up on the
very next call with no extra bookkeeping.
Implementations§
Source§impl<T> MtimeFileCache<T>
impl<T> MtimeFileCache<T>
Sourcepub fn new(capacity: usize, label: &'static str) -> Self
pub fn new(capacity: usize, label: &'static str) -> Self
Creates an empty cache holding at most capacity entries, labeled label for
diagnostics (e.g. "cargo config", "npm config") when the capacity is reached.
Sourcepub fn get_or_parse(
&self,
path: &Path,
parse: impl FnOnce(&str) -> T,
) -> Option<Arc<T>>
pub fn get_or_parse( &self, path: &Path, parse: impl FnOnce(&str) -> T, ) -> Option<Arc<T>>
Returns path’s parsed contents, from cache if path’s mtime is unchanged, else
re-reading and re-parsing with parse. None if path does not exist, is not a
regular file, exceeds MAX_CACHED_FILE_BYTES, or cannot be read.
Rejects anything but a regular file (a FIFO, socket, character device, or directory) as
observed at stat time — reading one of those can block the calling thread
indefinitely, and std::fs::metadata follows symlinks, so a symlinked regular file
still resolves. This check is necessarily a point-in-time observation, not a guarantee
about what crate::fs_probe::read_to_string_capped will see: a symlink that resolved
to a regular file at stat time can still be swapped to a FIFO before the subsequent
open (the same TOCTOU class as the size check below), which would still block on
open. Fixing that blocking-open race is out of scope here (it needs O_NONBLOCK or
equivalent); this doc only avoids overclaiming that the gate rules it out.
A file over MAX_CACHED_FILE_BYTES never reaches parse — every content-based
safety guard (nesting-depth, expansion) only sees content already read into memory, so
it cannot bound the read itself. This is enforced twice: the stat result is checked
first as a cheap pre-filter (skips opening an obviously huge file), and
crate::fs_probe::read_to_string_capped then bounds the read itself regardless of
what the stat reported — so a symlink swap or concurrent growth between the stat
and the read cannot let an oversized file’s content slip through (CWE-367).
Always performs one stat (the mtime check) — that cost is unavoidable and paid on
every call, cache hit or not — but reads and parses the file’s content only on a
miss. Compares mtime with !=, not >: a git checkout that restores an older file
moves the mtime backwards, and > would then keep serving the stale cached entry.
§Examples
use deps_core::mtime_cache::{DEFAULT_MAX_CACHED_FILES, MtimeFileCache};
use std::sync::Arc;
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("config.toml");
std::fs::write(&path, "value = 1").unwrap();
let cache: MtimeFileCache<String> = MtimeFileCache::new(DEFAULT_MAX_CACHED_FILES, "example");
let first = cache.get_or_parse(&path, str::to_owned).unwrap();
let second = cache.get_or_parse(&path, str::to_owned).unwrap();
assert!(Arc::ptr_eq(&first, &second), "an unchanged file is served from cache");