pub struct FileSystemStorage { /* private fields */ }Expand description
Filesystem-backed cache storage rooted at a directory.
Persists cached responses under a root directory so they survive process restarts. Each
response is two files: a <hash>.meta sidecar holding the CachePolicy and any trailers
as an rkyv-encoded binary blob, and a <hash>.body holding the raw body bytes and nothing
else. Bodies stream in and out — put writes to a temporary file the caller feeds
incrementally, and open streams the stored body back without loading it into memory. The
metadata is not human-readable; it is optimized for compact, fast loading rather than
inspection.
Defaults to a 1 GiB byte cap; override with
with_max_capacity_bytes or remove it with
unbounded. Optional time-based eviction is available through
with_time_to_idle and
with_time_to_live (off by default).
Clone is cheap — clones share the same root and capacity index, and see each other’s
writes.
§Layout
Entries live at <root>/<key-hash>/<variant-hash>.{meta,body}. The key hash is a SHA-256
of the request method and URL; the variant hash is a SHA-256 of the Vary signature, so
the multiple variants of one URL are sibling files in the same directory and get
enumerates them by reading that directory. Writing a variant that already exists replaces
it.
§Durability
Writes commit by renaming a fully-written temporary file into place, and the .meta is
written last — a reader treats it as the commit marker, so a half-written or abandoned entry
(a PutHandle dropped without finalize) is never visible to get.
§Capacity
A byte cap (1 GiB by default) bounds the total stored body size. When a write would push
the total past the cap, least-recently-used variants are evicted — their .meta and
.body files deleted — until the cache fits. The cap counts body bytes only, per variant,
matching the granularity of the on-disk layout. Reads count as use, so a frequently-served
variant outlives idle ones. Override with with_max_capacity_bytes or remove the cap with
unbounded.
The cap is tracked in an in-memory index built by scanning the root at construction, so it survives restarts (recency resets to whatever order the scan encounters). A directory that grew past the current cap under an older, unbounded configuration is trimmed to fit on the next construction.
§Expiry
Beyond the size cap, entries can be evicted on a timer:
with_time_to_idle drops variants not read within a duration,
with_time_to_live drops them a duration after they are stored.
Both delete the variant’s files on eviction, just like size eviction. This is best-effort
space reclamation rather than a hard read gate — get enumerates the
files on disk, so a just-expired variant may still be served in the brief window before its
files are deleted. It is never a correctness hazard: RFC 9111 freshness is enforced by the
Cache handler from the stored CachePolicy, independent of this
storage-level expiry. Both clocks are seeded at construction, so a reopened directory times
each entry from the reopen, not from its pre-restart history.
§Runtime
Filesystem access goes through the runtime selected by the smol, tokio, or async-std
feature. Enabling fs without one of those compiles but panics on use.
Implementations§
Source§impl FileSystemStorage
impl FileSystemStorage
Sourcepub fn new(root: impl Into<PathBuf>) -> Self
pub fn new(root: impl Into<PathBuf>) -> Self
Construct a storage rooted at root with a 1 GiB byte cap. The directory is created
on demand as entries are written; it need not exist yet. If it exists, it is scanned
to seed the capacity index, so previously stored entries count against the cap.
Sourcepub fn with_max_capacity_bytes(self, bytes: u64) -> Self
pub fn with_max_capacity_bytes(self, bytes: u64) -> Self
Set the maximum total stored body size, in bytes. Least-recently-used variants are evicted — their files deleted — when a write would exceed this cap. Defaults to 1 GiB. Re-scans the root, so a directory already over the new cap is trimmed to fit.
Sourcepub fn unbounded(self) -> Self
pub fn unbounded(self) -> Self
Remove the size cap. Stored bytes grow without bound. Useful in tests and short-lived processes; a cache living on shared disk should prefer the default capped configuration.
Sourcepub fn with_time_to_idle(self, duration: Duration) -> Self
pub fn with_time_to_idle(self, duration: Duration) -> Self
Evict entries that have not been read in this duration, deleting their files. Off by default.
This is best-effort space reclamation, not a read gate: get
enumerates the files on disk rather than the expiry index, so a just-expired variant may
still be served in the window before the eviction is processed and its files deleted. It
never serves stale content — RFC 9111 freshness is enforced by the
Cache handler from the stored CachePolicy, independent of this
storage-level expiry. (The in-memory backend’s idle eviction, by contrast, get
observes as a hard miss.)
The idle clock is seeded at construction: a reopened directory counts idle time from the reopen, not from each entry’s last read before the restart.
Sourcepub fn with_time_to_live(self, duration: Duration) -> Self
pub fn with_time_to_live(self, duration: Duration) -> Self
Evict entries this duration after their last insert regardless of access, deleting their files. Off by default.
Best-effort like with_time_to_idle: a just-expired variant
may be served until its files are deleted, but never past RFC 9111 freshness, which the
Cache handler enforces separately. This TTL is independent of that
freshness — an entry may be evicted while still fresh, or linger briefly past it.
The clock is seeded at construction, so a reopened directory counts each entry’s TTL from the reopen rather than its original store time.
Sourcepub fn weighted_size(&self) -> u64
pub fn weighted_size(&self) -> u64
Approximate total stored body size, in bytes, currently counted against the cap.
Eventually consistent — call run_pending_tasks first for
a settled value.
Sourcepub fn entry_count(&self) -> u64
pub fn entry_count(&self) -> u64
Approximate count of stored variants. Eventually consistent — call
run_pending_tasks first for a settled value.
Sourcepub async fn run_pending_tasks(&self)
pub async fn run_pending_tasks(&self)
Flush pending eviction bookkeeping, including deletion of files for evicted variants.
Call before reading weighted_size or
entry_count when an exact value matters.
Trait Implementations§
Source§impl CacheStorage for FileSystemStorage
impl CacheStorage for FileSystemStorage
Source§type PutHandle = FsPutHandle
type PutHandle = FsPutHandle
put.Source§type StoredEntry = FsStoredEntry
type StoredEntry = FsStoredEntry
get.Source§async fn get(&self, key: &CacheKey) -> Vec<Self::StoredEntry>
async fn get(&self, key: &CacheKey) -> Vec<Self::StoredEntry>
key. Returns an empty vec when
the key has no entries.Source§async fn put(
&self,
key: CacheKey,
policy: CachePolicy,
) -> Result<Self::PutHandle>
async fn put( &self, key: CacheKey, policy: CachePolicy, ) -> Result<Self::PutHandle>
key with the supplied policy.
Returns a PutHandle that the caller writes body bytes into,
then closes with PutHandle::finalize. If an existing entry
has the same Vary signature, finalize replaces it; otherwise
the new entry is appended. Read moreSource§async fn invalidate(&self, key: &CacheKey)
async fn invalidate(&self, key: &CacheKey)
key.Source§impl Clone for FileSystemStorage
impl Clone for FileSystemStorage
Source§fn clone(&self) -> FileSystemStorage
fn clone(&self) -> FileSystemStorage
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreAuto Trait Implementations§
impl !RefUnwindSafe for FileSystemStorage
impl !UnwindSafe for FileSystemStorage
impl Freeze for FileSystemStorage
impl Send for FileSystemStorage
impl Sync for FileSystemStorage
impl Unpin for FileSystemStorage
impl UnsafeUnpin for FileSystemStorage
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.