tests: build weak tables in a function that returns before collecting - #1830
Open
dg1sbg wants to merge 1 commit into
Open
tests: build weak tables in a function that returns before collecting#1830dg1sbg wants to merge 1 commit into
dg1sbg wants to merge 1 commit into
Conversation
The four weakness tests in hash-tables0.lisp allocate throwaway (list nil) temporaries in the same frame that then calls garbage-collect, and assert an exact hash-table-count afterwards. Boehm scans the stack conservatively, so a stale word left in that frame or in a register can still reference one of the temporaries and keep its weak entry alive; the count comes out one too high and the test fails. This is not a collector defect. A conservative collector promises never to collect live data, not to always collect dead data, and the comment above these tests already acknowledges they are stricter than that. The tests create the stale reference themselves by building and collecting in the same frame. Move construction into a builder that RETURNS first, so the frame holding the temporaries is popped before the collection. The assertion is unchanged: one collection, exact count. Measured on x86-64 Linux (LLVM 18, boehmprecise, bytecode) by calling the test body directly under 500 frames of recursion, 1000 trials per arm. The rate depends on code layout and varies between builds: across three builds the current form failed at 16.5%, 5.4% and 0.1%. The builder form failed 0 times in 2000 trials spanning the 5.4% and 0.1% builds, where about 55 failures would otherwise be expected. Non-vacuity checked: an entry whose key and value are both held in globals still yields a count of 2 and still fails, so a genuine leak is not masked. Previously submitted as clasp-developers#1815, which also retried the collection. The retry proves unnecessary and is omitted, so the test asserts exactly what it does today. Refs clasp-developers#1814.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes the
WEAK-KEY-AND-VALUE-WEAKNESSflake tracked in #1814. Supersedes #1815 (closed) — same diagnosis, but with a reproduction that PR did not have, and without the retry loop, which turns out to be unnecessary.Why this is a test bug, not a Clasp bug
The tests allocate throwaway
(list nil)temporaries in the same frame that then callsgarbage-collect, and assert an exacthash-table-count. Boehm scans the stack conservatively, so a stale word left in that frame or in a register can still reference a temporary and keep its weak entry alive.That is the defining trade-off of conservative collection: it guarantees it will never collect live data, not that it will always collect dead data. The comment already above these tests concedes as much:
Three things say stack artifact rather than a weak-table defect:
The only genuine Clasp-side fix would be precise stack scanning (shadow stacks or stack maps for every C++ and JIT frame), which Clasp has deliberately not adopted —
boehmpreciserefers to precise heap layout descriptors, not precise stack scanning.Reproduction
Call the test body directly, under recursion, and iterate. Roughly a minute; no special environment.
weakprobe.lisp— establishes the rate and its dependence on stack depthx86-64 Linux, LLVM 18,
boehmprecise,--build-mode=bytecode, 2000 trials per condition:worst-countis 2 in every failure — one cons falsely retained, never more.The rate is build-dependent. It comes from where stale words happen to land in registers and stack slots, so code layout moves it. The same commit rebuilt gave 5.4% instead of 16.5%, and another build 0.1%. Treat the numbers as a range (0.1 %–16.5 % at depth 500), not a constant — which is also why a handful of full-suite runs cannot establish absence: at ~1 % per run, twenty clean runs is the expected outcome of a measurement too weak to see it.
Why the retry from #1815 is not needed
#1815 did two things: had the builder return before collecting, and retried the collection up to ten times. Only the first removes the artifact; the second relaxes what is asserted, from "collected in one GC" to "collected within ten". Measured separately:
weak3.lisp— same-frame vs builder-only vs builder+retry1000 trials per arm at depth 500:
Builder-only is 0 failures in 2000 trials where roughly 55 would be expected. So the retry buys nothing measurable, and this PR omits it — the test asserts exactly what it does today, one collection and an exact count. It simply stops manufacturing the stale reference itself.
Non-vacuity
A fix that made the test unfailable would be worse than the flake. Pinning both key and value in globals —
:key-and-valueweakness drops an entry if either side dies, so pinning only the key proves nothing, which is a mistake I made on the first attempt at this control:Verification
Full build clean; regression suite run three times: 1963 successes,
TEST_EXIT=0,Passed WEAK-KEY-AND-VALUE-WEAKNESSeach time.Context
This one test was the sole failure in nine Linux CI jobs across six unrelated PRs in a single day — PRs touching koga scripts,
print-objectmethods, numeric compiler macros and CLOS dispatch guards, none of them near weak tables or the GC. It is also not confined toubuntu-latest/bytecodeas #1814's title suggests: it appears onnativetoo, and on bothclasp/andcando/variants, which is what you would expect of a stack-scanning artifact.