NAME

Punk::Cache::File - a cache store on disk, shared by the whole worker pool

SYNOPSIS

cache 'file', dir => '/var/cache/myapp', max_bytes => '512M';

DESCRIPTION

A file cache.

A key never becomes a path

A cache key is application data and often user data. ../../etc/passwd is a valid key, so is one with a NUL in it, so is a four-kilobyte one.

So the key is hashed, the hex sharded two levels deep, and the key itself stored in the entry and compared on read - which turns a hash collision into a miss rather than a silently wrong value.

Writes are atomic

An entry is written to a temporary file in its destination directory and then renamed. The same directory, because a cross-filesystem rename is not atomic and /tmp is very often another filesystem.

A reader therefore never sees a half-written entry, which is also why no checksum is needed: there is no torn state to detect. A crash mid-write leaves a temporary file, which the sweep removes.

The budget, and where the work happens

max_bytes is enforced by a sweep that drops expired entries and evicts the coldest until the cache is under budget.

That sweep is a full scan - about 270ms for 100,000 entries - so it never runs on a write. Each process counts what it has written and sweeps once that passes a slice of the budget, which bounds how often the scan happens without putting it in the request path. Between sweeps the cache can exceed max_bytes by roughly that slice.

A value too large for the budget is refused rather than stored, because making room would evict everything else and still not fit.

Single-flight

When a hot key expires under load, every worker misses at once. This store lets one of them compute while the others wait for the answer, using an exclusive lock file beside the entry.

It is best effort, deliberately:

  • A waiter that exhausts lock_wait computes anyway. Correctness never depends on the lock - duplicated work is a cost, a stalled request is an outage, and a request hanging because the winner died is worse than both.

  • A stale lock is stolen, not obeyed, or one crash poisons a key until somebody notices.

lock_wait defaults to five seconds.

Options

dir (required), max_bytes (default 512M), lock_wait (default 5).

The directory is created and checked for writability at construction - which is to_app - so a bad path is a boot failure in front of whoever deployed it, not a silent miss at three in the morning.

METHODS

The Punk::Cache backend contract: get, set, delete, clear, stats.

stats walks the cache to report bytes and entries, so it is an operator action rather than something to call per request.

SEE ALSO

Punk::Cache, Punk::Cache::Memory.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION <email@lnation.org>.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)