● Done — implemented in patch-prolog2, verified, and #4 updated. #5
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "file-cleanup"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What was implemented
build.rs — hashes libplg_runtime.a (FNV-1a 64, hand-rolled, zero deps) at
the moment it's located for include_bytes!, and bakes the digest in as
PLG_RUNTIME_HASH. Exactly the key you pushed for: derived from the bytes,
so dev rebuilds with a changed runtime get a new key, while identical
rebuilds keep reusing the cache — no cargo-version lies, no timestamp
noise, no runtime hashing cost.
link.rs::extracted_runtime() — the pid-keyed /tmp/plgc- is gone. Now:
~/.cache/plgc/runtime-ed158a59ce0c7e68/libplg_runtime.a
(concurrent-safe), then sweep stale runtime-* siblings — plgc's own cache
namespace, so reclaiming is legitimate.
via -L -lplg_runtime.
in-process compiles.
Verified
longer exists at all)
leftovers
One honest note: warm-cache runs time ~0.13s — about the same as before,
since clang dominates, not the 21MB write. The win here is steady-state
disk usage of exactly one ~21MB entry, forever, not speed.
What was implemented build.rs — hashes libplg_runtime.a (FNV-1a 64, hand-rolled, zero deps) at the moment it's located for include_bytes!, and bakes the digest in as PLG_RUNTIME_HASH. Exactly the key you pushed for: derived from the bytes, so dev rebuilds with a changed runtime get a new key, while identical rebuilds keep reusing the cache — no cargo-version lies, no timestamp noise, no runtime hashing cost. link.rs::extracted_runtime() — the pid-keyed /tmp/plgc-<pid> is gone. Now: ~/.cache/plgc/runtime-ed158a59ce0c7e68/libplg_runtime.a - Fast path: exists + length matches → reuse, no write at all. - Slow path: write to a process-unique temp name, atomic rename into place (concurrent-safe), then sweep stale runtime-* siblings — plgc's own cache namespace, so reclaiming is legitimate. - Hash in the directory name, canonical filename kept — because clang links via -L <dir> -lplg_runtime. - $XDG_CACHE_HOME → ~/.cache → temp-dir fallback; OnceLock retained for in-process compiles. Verified - 20 sequential runs → 1 cache dir, 0 /tmp/plgc-* dirs (the pattern no longer exists at all) - 20 concurrent cold-cache runs → 20/20 succeed (exit 1), 1 dir, 0 temp leftovers - Planted fake runtime-deadbeef… → swept on next link - patch-prolog2's full just ci → ✅ One honest note: warm-cache runs time ~0.13s — about the same as before, since clang dominates, not the 21MB write. The win here is steady-state disk usage of exactly one ~21MB entry, forever, not speed.Review
Verified on the branch:
just cigreen; 8-way concurrent cold-cache start → all succeed, exactly oneruntime-<hash>dir, no temp leftovers; warm reuse works; hash derivation point is correct (same fileinclude_bytes!reads, content-keyed not version-keyed); FNV-1a is the right tool for invalidation and the comment honestly says so. The atomic temp+rename install is sound (same fs, sorenameis atomic).Five findings, in priority order:
1. Orphaned
.libplg_runtime.a.<pid>temps are never cleaned — and the motivating scenario produces them (bug)A process killed between
fs::write(&tmp, …)andfs::renameleaves a 21MB orphan inside the kept dir, andsweep_stale_runtimesonly removes sibling dirs — the orphans survive every future sweep. Reproduced:Issue #4's dynamics — a watcher invoking plgc 7×/s — is exactly a kill-mid-extraction generator (each save can interrupt a cold-cache run). So the "steady-state disk usage of exactly one ~21MB entry, forever" claim doesn't hold under the very workload this PR fixes; it just degrades slower. Suggested fix: when sweeping, also remove
.libplg_runtime.a.*entries in the kept dir whose mtime is older than a few minutes (the age gate avoids racing a live writer about to rename).2. The sibling sweep can fail a concurrently running different plgc build (the "self-healing" claim is too strong)
Window: old-build plgc passes the fast path (or finishes extraction), new-build plgc sweeps its dir, old plgc's clang then can't find
-lplg_runtime→ that invocation fails with a link error — not self-healing for the victim, and a long-lived embedder ofplgc's lib would keep failing (theOnceLockmemoizes the now-deleted path for the process lifetime). Mixed-build concurrency is routine on a dev box:cargo test-built plgc sweeping while the installed plgc is mid-link in another terminal. Cheap fix that also preserves the steady-state goal: age-gate the sibling sweep (only removeruntime-*dirs with mtime older than ~7 days). Disk stays bounded; the race now requires actively linking with a week-old build at the exact moment of a sweep.3. The HOME-less fallback is cross-user shared with a predictable name (security)
temp_dir().join("plgc-cache")on a system withoutTMPDIRis/tmp/plgc-cache— world-writable parent, predictable path, and the hash in the dir name is public (deterministic from released runtime bytes). Another user can pre-plantruntime-<hash>/libplg_runtime.a; the reuse check is length-only, which a padded malicious archive satisfies — attacker code linked into the victim's binaries. Given this project's whole attested-immutable-binary posture, the fallback should be per-user: suffix the uid (plgc-cache-<uid>) and/or verify content hash on reuse in the fallback path. (~/.cacheis fine as-is — user-owned.)4. Optional hardening: verify content on reuse
The cache is content-named but not content-verified: reuse trusts dir name + length. Re-hashing 21MB with the same FNV on the fast path costs ~10ms against clang's seconds, and would make corruption/poisoning self-correcting everywhere, not just in the fallback. Optional in
~/.cache; I'd consider it required for the temp fallback (finding 3).5. Doc staleness (one line)
docs/ARCHITECTURE.md:27still says the archive is "extracted to a temp dir, passed to clang, and deleted" — needs the cache-scheme wording.Overall: the design is right (content-keyed shared cache is exactly what #4 needs) and the implementation is careful where it's careful (atomic install, build-time hashing, XDG chain). Findings 1–2 are small, surgical changes to the sweep; 3 is a one-line path change. With those, this closes #4 properly.
Review assessment — findings applied on this branch
All five findings were assessed; 1, 2, and 5 confirmed and applied as suggested
(one refinement), 3 applied with a stronger variant, 4 declined with rationale.
Applied
1. Orphaned temps (confirmed by repro — planted orphans survived warm runs,
66M cache). Fixed with one refinement to the suggested patch: the sweep now
runs before the fast path inside the
OnceLockinit, not on the slow path— otherwise warm runs (the common case) would never reclaim orphans. Aged
.libplg_runtime.a.*entries (>10 min) in the kept dir are removed;fresh ones are spared so a live writer about to
renameis never raced.Verified: 1-hour-old orphan reclaimed on a warm run, fresh temp untouched.
2. Sibling sweep race. Age-gated as suggested:
runtime-*siblings areonly removed once older than 7 days. Disk stays bounded at "builds actually
used this week"; sweeping a running older build now requires it to be a
week old at the exact moment of a cold-start sweep. Verified: 8-day-old
sibling swept, fresh sibling kept.
3. HOME-less fallback (applied, stronger variant). The suggested
uid-suffix is just as predictable (
/tmp/plgc-cache-1000can be pre-planted),and FNV content-verification is not adversary-proof — FNV-1a is malleable, so
a padded malicious archive can match length and hash. Instead the fallback
now shares nothing: with no
$XDG_CACHE_HOME/$HOME, the archive isextracted into a per-link
tempfile::TempDir(private, unpredictable name)held across the clang call and removed on drop. ~21MB per link in that rare
environment buys zero attack surface. Verified: run succeeds, no
/tmp/plgc-cache, zero/tmpentries left behind.5. Doc staleness.
docs/ARCHITECTURE.mdnow describes thecontent-addressed cache + age sweeps + ephemeral fallback (the old text's
"and deleted" was never true even pre-PR).
Declined
4. Content verification on reuse — with finding 3 resolved by
not-sharing, the "required for the fallback" half is moot; in user-owned
~/.cachethe remaining threat is corruption, which the atomicrename + length check already cover. And the same FNV-malleability point
means re-hashing wouldn't deliver the poisoning protection it implies, at
the cost of a 21MB read on every warm run. If verification is ever wanted,
it should be a cryptographic digest — a different, deliberate change.
State
just ci✅ on the branch after the changes; full behavior matrix re-verified(warm reuse, cold concurrent, orphan sweep, sibling age gate, ephemeral
fallback).