Class DiskCacheBudget
Byte-budget and least-recently-used bookkeeping for a directory of cache files, plus the atomic read/write primitives every disk cache shares.
public sealed class DiskCacheBudget
- Inheritance
-
DiskCacheBudget
- Inherited Members
Remarks
The budget keeps an in-memory index of the entries under
Directory (path, size, recency) and a running total. The
directory is scanned once, lazily, on the first write. After that, writes
and reads update the index in memory, and eviction picks victims from it
without enumerating or stat-ing the directory. Cost per write is
therefore independent of how many files the cache holds.
The index can drift when something else changes the directory (a host "clear caches" sweep, or another process sharing the cache). It is reconciled with a rescan when an eviction finds its victim already gone, and otherwise at most once per reconcile interval. Between rescans the cap is soft: another process's writes can push the directory past it.
Recency is an in-process sequence number, so eviction order is exact even
when several entries are written within the file system's timestamp
resolution. Entries found by a scan are ordered by their last-access time
and rank older than anything this process has written or read. Reads and
writes stamp the file's last-access time, so the order survives a restart
even on noatime mounts.
All members are thread-safe. Reads never take the lock; a write takes it only to update the index and, when over budget, to evict.
Constructors
DiskCacheBudget(string, string, long, bool, TimeSpan?)
Creates a budget over the fileExtension files under
directory. Nothing is read from disk until the first
TryWrite(string, ReadOnlySpan<byte>, Func<bool>?) or
RecordWrite(string, long).
public DiskCacheBudget(string directory, string fileExtension, long maxBytes, bool recursive = false, TimeSpan? reconcileInterval = null)
Parameters
directorystringCache directory. Assumed private to the cache: every
fileExtensionfile under it counts toward the budget and may be evicted, and stale*.tmpfiles are removed.fileExtensionstringExtension of entry files, including the leading dot (for example
.dlist).maxByteslongSoft upper bound on the total size of entry files. Must be positive.
recursiveboolWhether entries live in subdirectories of
directory.reconcileIntervalTimeSpan?Minimum time between reconciling rescans; null uses DefaultReconcileInterval.
Exceptions
- ArgumentException
directoryorfileExtensionis null or empty.- ArgumentOutOfRangeException
maxBytesis not positive.
Fields
DefaultReconcileInterval
Default interval between reconciling rescans of the directory.
public static readonly TimeSpan DefaultReconcileInterval
Field Value
Properties
Count
The number of indexed entry files. Zero until the first write loads the index.
public int Count { get; }
Property Value
Directory
The cache directory this budget governs.
public string Directory { get; }
Property Value
MaxBytes
Soft upper bound, in bytes, on the total size of entry files.
public long MaxBytes { get; }
Property Value
TotalBytes
The indexed total size of entry files, in bytes. Zero until the first write loads the index.
public long TotalBytes { get; }
Property Value
Methods
EnsureIndexed()
Loads the index from disk if no write has loaded it yet, so TotalBytes and Count report what the directory holds rather than zero.
public void EnsureIndexed()
RecordAccess(string)
Marks the indexed entry at path most recently used.
A path the index does not hold yet is ignored; it is picked up by the
next scan. Lock-free.
public void RecordAccess(string path)
Parameters
pathstringFull path of the entry file.
RecordWrite(string, long)
Records that the entry at path now holds
length bytes and is the most recently used, then
evicts least-recently-used entries until the total is within
MaxBytes. The first call loads the index from disk.
public void RecordWrite(string path, long length)
Parameters
pathstringFull path of the entry file that was just written.
lengthlongSize of the entry file in bytes.
TouchAccessTime(string)
Sets a file's last-access time to now, so a later scan (after a restart) orders it as recently used without relying on file-system access-time tracking. Never throws.
public static void TouchAccessTime(string path)
Parameters
pathstringThe file to stamp.
TryDelete(string)
Deletes a file if present, ignoring any IO error.
public static void TryDelete(string path)
Parameters
pathstringThe file to delete.
TryRead(string)
Reads the entry at path and marks it most recently
used, or returns null when it is absent or unreadable.
Never throws for IO errors.
public byte[]? TryRead(string path)
Parameters
pathstringFull path of the entry file.
Returns
- byte[]
TryWrite(string, ReadOnlySpan<byte>, Func<bool>?)
Atomically writes bytes as the entry at
path (temp file in the same directory, then rename),
records it, and evicts least-recently-used entries if the budget is
exceeded. Never throws for IO errors.
public bool TryWrite(string path, ReadOnlySpan<byte> bytes, Func<bool>? beforeCommit = null)
Parameters
pathstringFull path of the entry file; its directory is created if absent.
bytesReadOnlySpan<byte>The entry content.
beforeCommitFunc<bool>Optional check run after the temp file is written and before it is renamed into place; returning false abandons the write.