WEAK-KEY-AND-VALUE-WEAKNESS (src/lisp/regression-tests/hash-tables0.lisp:147) fails intermittently in CI. Same commit, same job, opposite results:
| run |
commit |
clasp/ubuntu-latest/bytecode/no/no |
| clasp-developers/clasp 30693073178 |
661438586 |
"Run regression tests" failed — Failures: WEAK-KEY-AND-VALUE-WEAKNESS, Successes: 1967 |
| dg1sbg/clasp 30697205345 |
661438586 (identical) |
"Run regression tests" passed — Successes: 1968 |
(The fork run's job still shows red, but for an unrelated reason: the runner received a shutdown signal during the later ANSI step. Its regression-test step succeeded.)
Only this OS/mode combination is affected. In the upstream run above, clasp/macos-latest/bytecode and clasp/ubuntu-latest/native both passed with the same commit.
Why it is fragile
(test weak-key-and-value-weakness
(let ((table (make-hash-table :weakness :key-and-value)))
(setf (gethash (list nil) table) :value
(gethash :key table) (list nil)
(gethash (list nil) table) (list nil)
(gethash :key table) :value)
(gctools:garbage-collect)
(hash-table-count table))
(1))
The test allocates three throwaway (list nil) values, triggers one GC, and asserts the count is exactly 1. Under Boehm's conservative stack scanning, a single stale word in a register or stack slot still referencing one of those temporaries keeps its entry alive and the count comes out 2. Whether that happens depends on register allocation and stack residue at the call site, so it is sensitive to unrelated codegen changes and to the state left behind by the ~20 suites that run before hash-tables0.
weak-key-weakness immediately above it has the same shape and presumably the same exposure.
Not reproducible locally
macOS arm64, both variants, all clean:
- 200/200 iterations of the test body in a fresh image
- 5/5 runs of
TEST_SUITES=fastgf,clos,mop,hash-tables0
- full suites: boehm 1977, boehmprecise 1979, zero unexpected failures, including a from-scratch rebuild
Suggestion
Rather than asserting an exact count after one GC, either assert the surviving entry is present ((gethash :key table) → :value) and treat extra entries as tolerable, or retry the collect a bounded number of times before asserting. As written the test can redden any PR at random, which makes red CI easy to dismiss — the failure mode you least want on a project where CI is the stability gate.
Found while working on #1811 / #1812; unrelated to that change, which touches only CLOS class-identity guards and creates no new retention path (verified: the eql-specializer intern table is unchanged by hash-table use, and weak tables still collapse to 1 after 500 EQL dispatches with fully populated call histories).
WEAK-KEY-AND-VALUE-WEAKNESS(src/lisp/regression-tests/hash-tables0.lisp:147) fails intermittently in CI. Same commit, same job, opposite results:clasp/ubuntu-latest/bytecode/no/no661438586Failures: WEAK-KEY-AND-VALUE-WEAKNESS, Successes: 1967661438586(identical)(The fork run's job still shows red, but for an unrelated reason: the runner received a shutdown signal during the later ANSI step. Its regression-test step succeeded.)
Only this OS/mode combination is affected. In the upstream run above,
clasp/macos-latest/bytecodeandclasp/ubuntu-latest/nativeboth passed with the same commit.Why it is fragile
(test weak-key-and-value-weakness (let ((table (make-hash-table :weakness :key-and-value))) (setf (gethash (list nil) table) :value (gethash :key table) (list nil) (gethash (list nil) table) (list nil) (gethash :key table) :value) (gctools:garbage-collect) (hash-table-count table)) (1))The test allocates three throwaway
(list nil)values, triggers one GC, and asserts the count is exactly 1. Under Boehm's conservative stack scanning, a single stale word in a register or stack slot still referencing one of those temporaries keeps its entry alive and the count comes out 2. Whether that happens depends on register allocation and stack residue at the call site, so it is sensitive to unrelated codegen changes and to the state left behind by the ~20 suites that run beforehash-tables0.weak-key-weaknessimmediately above it has the same shape and presumably the same exposure.Not reproducible locally
macOS arm64, both variants, all clean:
TEST_SUITES=fastgf,clos,mop,hash-tables0Suggestion
Rather than asserting an exact count after one GC, either assert the surviving entry is present (
(gethash :key table)→:value) and treat extra entries as tolerable, or retry the collect a bounded number of times before asserting. As written the test can redden any PR at random, which makes red CI easy to dismiss — the failure mode you least want on a project where CI is the stability gate.Found while working on #1811 / #1812; unrelated to that change, which touches only CLOS class-identity guards and creates no new retention path (verified: the eql-specializer intern table is unchanged by hash-table use, and weak tables still collapse to 1 after 500 EQL dispatches with fully populated call histories).