Skip to content

Stale %form / %intAct / %image caches when a source filename is rewritten #28

Description

@cnighswonger

Found during Codex review of #26. Pre-existing on master — not introduced by that PR, and reproducible without it.

Problem

#26 fixes stale %processed / %behandlad for a rewritten source filename. But three more caches hold source-derived state keyed by a filename-derived prefix and are not cleared by prFile():

Cache Key Cleared by prFile()? Cleared by prInitVars()?
%processed, %behandlad $infil ✅ (as of #26)
%knownToFile $fSource ✅ (line 351)
%form $fSource = "${infil}_${sidnr}" ✅ (line 3441)
%intAct $fSource
%image $iSource = "${fSource}_${bildnr}"

Note prFile() already clears %knownToFile but not %form — the same asymmetry that caused #21/#22.

Reproduction

use PDF::Reuse;
sub build { my ($f,$l)=@_; prFile($f); prFontSize(24); prText(72,700,$l); prEnd(); }

build('t.pdf','ALPHA');
prFile('o1.pdf'); prForm('t.pdf'); prEnd();

sleep 2;                      # ensure the rewrite lands in a later second
build('t.pdf','BRAVO');       # same filename, new content
prFile('o2.pdf'); prForm('t.pdf'); prEnd();   # <-- dies
pass2 DIED:  1785272954 ne 1785272952 aborts

%form{$fSource}[fID] caches the source file's mtime; byggForm() (line 5386) hard-aborts when the current mtime disagrees. Since %form survives prFile(), the stale entry persists across documents.

The sleep 2 is required: without it the rewrite usually lands in the same second and the mtime comparison passes by luck. Same whole-second stat granularity documented in #26.

Severity — lower than #21/#22

This fails loudly with a diagnostic naming both timestamps, rather than producing a corrupt PDF. Content correctness was verified for the same-second case: after a rewrite, prForm emitted the new content (o2.pdf contained BRAVO, not stale ALPHA), so this appears to be a spurious-abort bug, not silent corruption. That should be confirmed more broadly before anyone relies on it.

prInitVars() between documents avoids it entirely.

Why this wasn't folded into #26

%form entries are consumed during emission[fVALID] at lines 683/701, [fIMAGES] at 743/3507, [fOBJ] at 3508. The invalidation hook added in #26 (dropChangedCache) runs at seven cache-fill sites, which can fire mid-document. Deleting %form{$fSource} from there risks pulling state out from under an in-flight write — a corruption risk strictly worse than the spurious abort it would fix.

So this needs its own design rather than a one-line extension. Options worth weighing:

  1. Invalidate in prFile() alongside %knownToFile, deleting every key with the rewritten filename's prefix. Matches where %knownToFile is already handled, and prFile() is a document boundary, so nothing is in flight. Requires a prefix scan (grep /^\Q$name\E_/) since keys are compound.
  2. Extend the #26 self-write invalidation to sweep the same prefixes — same placement, same document-boundary safety.
  3. Relax the byggForm mtime abort to re-read rather than die. Fixes the symptom but discards a deliberate consistency check; would need to be sure the rest of %form gets refreshed.

Option 1 or 2 look right; the prefix scan is the only real cost, and these hashes are small.

Also worth fixing nearby

analysera() calls its cache preamble (and, since #26, dropChangedCache) before findGet(), which can rewrite $infil — whereas getPage() calls findGet() first. For ordinary prDoc($path) this is harmless because findGet returns the path unchanged for existing local files, but on the prCid replay path (file missing, checkId set → returns $fil . $cid) the cache key, the glob-aliased hash, the opened file, and the final $processed{$infil}{root} assignment can diverge. Pre-existing ordering; called out by the same review. Low priority, but it should be made consistent with getPage() when this area is next touched.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions