Expand description
Content-addressed graphics cache.
Every convert_image / convert_image_svg call rasterises a
self-contained source file (PDF / EPS / PNG / SVG) with a small set
of render-shaping inputs (page, density, target format). Across a
canvas of arXiv submissions the same byte content recurs constantly
— common journal logos, reused author-affiliation marks, and
regenerated runs of the same paper. PERFORMANCE.md §5 records the
graphics phase as 36.5% of corpus wall, so a cache that hits
even a third of the time shaves ~11% off corpus wall.
§Heritage: Perl LaTeXML.cache
The Perl post-processor stores a tied BerkeleyDB hash named
LaTeXML.cache per output directory. Keys are built from the
processor class, the source path, and the transform string; values
are "dest|width|height" strings. The features we mirror are
listed below.
- Disable flag (Perl:
nocache) → envLATEXML_GRAPHICS_CACHE_OFF. - Re-use across runs (Perl: one .cache per dest dir) → global XDG cache, shared across all conversions on the host. This is a strict superset of the Perl behaviour: cross-paper sharing, plus reproducible content-keying instead of path-keying.
- Source-staleness check (Perl compared source mtime to cached output mtime) → unnecessary here: the cache key is SHA-256 of the source bytes, so any source edit produces a different key.
- Cached dimensions (Perl: width/height in the cached value) → sidecar
<hash>.<ext>.dimsfile containingwidth\nheight\n. Lookup returns(success, dims); callers skip theread_image_dimensionssyscall on hits.
Beyond Perl: content-hash keying gives stricter staleness; XDG placement gives cross-paper reuse; multi-process safety (below) lets canvas sweeps share the cache without corruption.
§Cache key
SHA-256 of:
source bytes ‖ page ‖ density ‖ target-extension
The page/density bytes are appended as 8-byte little-endian; the extension is appended as ASCII lower-case bytes. Bytes-only keying makes the cache reproducible across machines and gives near-zero collision risk for our use.
§On-disk layout
$XDG_CACHE_HOME/latexml-oxide/graphics/<aa>/<full-hash>.<ext>
$XDG_CACHE_HOME/latexml-oxide/graphics/<aa>/<full-hash>.<ext>.dims
$XDG_CACHE_HOME/latexml-oxide/graphics/.prune.lock
Sharded by the first two hex characters of the hash to keep any one directory’s entry count bounded (worst case ~1/256th of the total cache size).
$XDG_CACHE_HOME falls back to $HOME/.cache per the XDG spec.
§Multi-process safety
The cache is designed to be shared by parallel cortex_worker
processes within a canvas sweep. Concurrency guarantees:
- Concurrent writes to the same key: each writer renders to a private
<final>.tmp.<pid>.<nanos>sidecar then issues a singlerename(2)into the final path.renameis atomic on the same filesystem; if two writers race, the lastrenamewins, and because both sources are byte-equivalent the outcome is correct.link_or_copyretries once onEEXISTfor the same reason. - Concurrent reads + writes: a reader hardlinks the cached file into the destination. POSIX
link(2)either succeeds (returning a new directory entry that survives any subsequentunlinkof the source) or fails cleanly. If the cache file is unlinked between hash computation and link, the reader gets a miss and falls through — no data corruption. - Concurrent prunes: prune holds
flock(LOCK_EX | LOCK_NB)on.prune.lock. If another process already holds the lock, this one skips its prune attempt. Only one prune runs at a time per machine. The prune itself walks the dir, sorts by mtime, deletes oldest until under cap — and toleratesENOENT(another writer could have re-used the entry concurrently). - Hardlinked dest ↔ cache: when
link_or_copyhardlinks the cache file into the destination, that hardlink is independent of the cache lifecycle. Even if a prune deletes the cache entry afterwards, the destination’s hardlink keeps the data alive on the filesystem.
These guarantees hold on any POSIX filesystem. On Windows (which
lacks robust flock), the prune lock is a best-effort OpenOptions
create-new sentinel — same correctness, slightly more retry traffic.
§Lifecycle
- Hit: hardlink the cache file into the destination (zero-copy on the same filesystem). If
hardlink fails (cross-filesystem, EXDEV), fall back to a regular file copy. Read the
.dimssidecar if present. - Miss-then-success: copy the produced destination back into the cache. Write the
.dimssidecar alongside. - LRU prune: each insertion checks the on-disk total against the cap
(
LATEXML_GRAPHICS_CACHE_MAX_MB, default 2048 = 2 GB). When over cap, holds the prune lock, sorts entries by mtime ascending, deletes until under cap. File access on read also refreshes the mtime so frequently-hit entries survive.
§Disable / tune
LATEXML_GRAPHICS_CACHE_OFF=1— bypass entirely (read+write both skipped). The wrapper devolves to the bare conversion call.LATEXML_GRAPHICS_CACHE_DIR=/path— override the cache directory.LATEXML_GRAPHICS_CACHE_MAX_MB=N— cache size cap.
Structs§
- Cache
Hit - Result of a cache lookup.
- Cached
Dims - Cached dimensions for an image. Mirrors Perl
LaTeXML.cache"dest|width|height"value triple (we already have the dest path at the call site — only the dimensions need round-tripping). - Render
Key - Render-shaping inputs that go into the cache key alongside source
bytes. Two calls with the same
RenderKeyMUST produce byte-equivalent output (modulo metadata variation tools like ImageMagick are known to introduce — seecompare_outputs_strictaudit in the spawn paths).
Enums§
- Cache
Policy - Whether a given conversion consults the shared graphics cache.
- Convert
Result - Three-state result from a cached conversion. Disambiguates the two
failure modes that would otherwise collapse to
None:
Functions§
- lookup
- Look the cache up. On hit: hardlink/copy into
dest, refresh the cache entry’s mtime, returnSome(CacheHit{dims}). On miss or any I/O hiccup, returnNoneso the caller falls through to a real conversion. - stats
- Return
(hits, misses)since process start. Useful for telemetry and the post-run summary log line. - store
- Insert
dest(and optionally its dimensions) into the cache under(source, key). Idempotent and best-effort: any I/O failure leaves the cache unchanged. - with_
cache - Bytes-only cache wrapper. Use when the caller doesn’t need
dimensions cached (e.g. SVG path where viewBox dims are cheap to
re-read from disk). Returns
trueon success. - with_
cache_ dims - Cache-aware wrapper around any
(source, dest) -> boolconversion that also wants to round-trip dimensions through the cache.