fix(tests): replace sleep-race concurrency proofs in RetailDatLoaderTests

Two tests proved "these two unrelated DAT reads ran concurrently" by
racing a fixed Thread.Sleep(40) window against .NET thread-pool
scheduling latency for a second Task.Run. Under the CPU contention of
a full `dotnet test AcDream.slnx` run (all 9 test projects' VSTest
hosts launch concurrently) plus a busy machine, thread-pool injection
can occasionally miss the window, making MaxConcurrentReads read 1
instead of 2 and failing the assertion with no underlying code defect.

RetailAnimationLoader and RetailPhysicsScriptLoader both coalesce
same-key reads correctly via ConcurrentDictionary<K, Lazy<T>>.GetOrAdd,
which is atomic and timing-independent (verified by reading, not just
running) - only the test's method of proving cross-key overlap was
timing-fragile. DecodedTextureCacheTests already uses the correct
deterministic-gate pattern; this brings RetailDatLoaderTests in line
with it via a Barrier-backed rendezvous instead of a sleep race.

Filed as #248 (docs/ISSUES.md) with the full attempt matrix: could not
catch the originally-reported AcDream.Content.Tests failure in the act
despite ~72 Content.Tests executions across four contention strategies
over ~30 full-suite-equivalent runs, though the general mechanism
reproduced 3x in AcDream.App.Tests's already-known zero-allocation
flake class (left untouched, out of scope here).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-30 10:57:01 +02:00
parent 3b5e099241
commit dc0468cc2b
2 changed files with 50 additions and 7 deletions

View file

@ -488,7 +488,23 @@ tree is recoverable from git history at `844cf092^`.
## #255 — Two RetailDatLoader concurrency tests measured the thread pool, not the loader
**Status:** REOPENED 2026-07-29 — the `LongRunning` fix is a hint, not a
**Status:** DONE — 2026-07-30 (Campaign P adjacent; flake hunt). The
sleep-race class was root-caused and fixed for good: the two
`RetailDatLoaderTests` concurrency proofs raced a fixed
`ReadDelayMilliseconds=40` window against thread-pool injection latency —
under full-solution CPU contention (9 concurrent VSTest hosts) the second
`Task.Run` can miss the window, observe `MaxConcurrentReads==1`, and fail
(the Slice-P3 one-in-three full-suite failure). Production coalescing
(`ConcurrentDictionary<K, Lazy<T>>.GetOrAdd`) verified NOT racy. Fix:
deterministic `RawDatabase.ArmConcurrencyGate(n)` (a `Barrier` rendezvous
inside `TryGetFileBytes`) replaces the sleep race — the same pattern
`DecodedTextureCacheTests` already uses. Proof: 3x full solution suite
clean + 20x isolated filtered runs clean. Hunt attempt matrix: ~72
Content.Tests executions across four contention strategies never caught
the original failure live; root cause established by static analysis of
the only wall-clock-dependent file in the project. (An earlier
worktree-based writeup of this fix was drafted against a stale base as a
new issue; reconciled into this entry — no separate issue number.)
guarantee; `AnimationCache_Coalesces…` still fails under full-suite load on
Windows. Reopened independently by two sessions on the same day; both evidence
sets are kept at the end of this issue.