plgc: cache the extracted libplg_runtime.a instead of re-extracting ~21MB per run #4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
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?
Summary
Every
plgc run/buildextracts the embeddedlibplg_runtime.a(~21MB) to afresh temp dir before linking. For tools that invoke
plgcat high frequency(loglings runs
plgc run <file> --query teston every editor save), this is~21MB of extract+write per call, and the per-run temp dirs are what turn a
misbehaving caller into a disk-space incident.
Context
A loglings watcher bug briefly invoked
plgc run~7×/second. The side effectwas
/tmpfilling with 1696plgc-*temp dirs (~36GB), each containingonly the freshly-extracted
libplg_runtime.a— i.e. plgc had extracted theruntime but the dir was never reclaimed.
On reproducibility
Normal cleanup looks robust — I could not reproduce the leak in isolation:
single run, 30× rapid-sequential, 30× concurrent, SIGINT-mid-run, and
SIGKILL-mid-run all clean up (temp-dir delta 0). The accumulation appears tied
to sustained high-frequency invocation and/or low-disk conditions (once /tmp
fills, cleanup that writes/renames can itself start failing, compounding). So
this is less "cleanup is broken" and more "the per-run extraction is a fragile,
expensive design under load."
Suggestion
Extract
libplg_runtime.aonce to a stable, content-addressed cache andreuse it across runs, instead of a fresh per-run temp copy — e.g.
$XDG_CACHE_HOME/plgc/runtime-<version-or-hash>.a(fall back to$TMPDIR/plgc-runtime-<version>/). Benefits:plgc runcallers),
persist, not be reclaimed each run).
The loglings-side trigger (a watcher feedback loop) is already fixed; filing
this because the per-run re-extraction is a real efficiency issue for any
frequent caller, and a cached runtime would have kept the incident from ever
reaching 36GB.
Reported against
plgc0.1.0.Root cause found — the leak is unconditional, not load-dependent.
crates/compiler/src/link.rs::extracted_runtime()extracts the embeddedruntime to a pid-keyed dir:
and nothing in the codebase ever removes it (
remove_dir_allappears nowhere;the
tempfile::tempdir()cleanups in main.rs cover only the output binary).So every plgc process that links leaks one ~21MB
/tmp/plgc-<pid>dir —single runs included. loglings sees ~27 new dirs per watch start (one per
exercise compiled in its startup scan).
My earlier "cleanup looks robust / can't reproduce in isolation" was a
measurement artifact: with 1696 leaked dirs spanning a wrapped pid range, new
processes collided with existing dir names (
create_dir_allis idempotent),so the dir count didn't move.
Concrete fix (and it makes runs faster, too): key the dir by version
instead of pid, so every process reuses one stable extraction:
libplg_runtime.aalready exists with the right length, use it (skip the21MB write entirely);
renameinto place(atomic — safe under concurrent plgc processes);
OnceLockalready prevents repeat work within a process.The pid-keyed comment says it avoids races between concurrent processes — the
write-then-rename gives the same safety with a single reusable path and zero
accumulation.
(duplicate of the comment above — posted twice by tooling, please ignore)
Implemented (working tree, pending commit). Final design — refined from the
discussion: the cargo version is not a valid cache key (dev rebuilds embed
different bytes under the same version), and a binary hash / build timestamp
over- invalidate. The exact key is a build-time FNV-1a hash of the archive
bytes themselves.
build.rs: hasheslibplg_runtime.awhile locating it forinclude_bytes!and emits
cargo:rustc-env=PLG_RUNTIME_HASH=<16-hex>. Deterministic,dependency-free, zero runtime cost;
rerun-if-changedalready covers it.link.rs::extracted_runtime(): materializes the archive at$XDG_CACHE_HOME/plgc/runtime-<hash>/libplg_runtime.a(→~/.cache/plgc,→ temp-dir fallback for HOME-less environments). Fast path reuses an
existing extraction (length-checked); slow path writes a process-unique
temp name and atomically
renames into place, then sweeps staleruntime-*siblings. The archive keeps its canonical name because clanglinks via
-L <dir> -lplg_runtime; the hash lives in the directory name.The per-process
OnceLockis retained for parallel in-process compiles.Verified:
/tmp/plgc-*dirsruntime-deadbeefdeadbeefremoved on next linkjust cigreenNet effect: nothing accumulates anywhere, and warm runs skip the 21MB write
entirely (loglings' per-save checks get faster as a side effect).