* fix(3803): replace wall-clock polls with Atomics.wait barrier (mirrors c22e869b)
Three execFileSync('sleep', ...) wall-clock deadline spins in
graphify-auto-update tests replaced with Atomics.wait-based atomicSleep
helper. This is the same pattern established in c22e869b (PR #3790) for
locking-bugs:180.
- cleanupHookRepo: 50 ms Atomics.wait steps instead of spawning a POSIX
sleep process per iteration while waiting for .rebuild.lock to clear.
- "completes to status=ok" poll: 100 ms Atomics.wait steps instead of
execFileSync('sleep', ['0.1']) while waiting for the detached rebuild
process to write the final status file.
- "completes to status=failed" poll: same fix.
The ceiling deadlines (4 s / 15 s) remain as timeout guards — they are
not the synchronization primitive. The CPU-yielding wait is now
Atomics.wait so no external process is spawned per iteration.
All 36 tests in the suite pass (node --test tests/graphify-auto-update.test.cjs).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(3803): replace wall-clock deadline polls with behavior-anchored iteration counts
The previous commit (752a5684) replaced execFileSync('sleep') with
Atomics.wait but kept the `Date.now() + 15000` wall-clock deadline
pattern in the two assertion-path polls. That is still the timing-flake
antipattern described in #3803: the loop bound is an absolute time, not a
function of the mock's known behavior.
This commit removes both `const deadline = Date.now() + 15000` /
`while (Date.now() < deadline)` loops and replaces them with
iteration-count bounds derived from the mock's declared sleepMs:
waitBudget = sleepMs + 2000 (2 s covers two bash spawn overheads:
hook script + detached rebuild subprocess)
maxIter = ceil(waitBudget / 100) (100 ms poll step)
The 2 s buffer absorbs process spawn + filesystem write latency without
anchoring to an absolute wall-clock value. If the budget is exhausted the
assertion below fires with a clear diagnostic instead of a silent
time-dependent pass.
Same antipattern fixed by PR #3793 (bug-1974-context-exhaustion-record).
Verified: 3 consecutive local runs, 0 failures each (~80-100 s/run).
Anti-pattern sweep results (Date.now() + N in tests/):
- tests/locking-bugs-1909-1916-1925-1927.test.cjs:291,448 — coordination
BARRIER backstops (wait for both subprocesses to reach gate), not
completion polls; different pattern, out of scope for this PR.
- tests/graphify-auto-update.test.cjs:286 — cleanupHookRepo backstop
(not an assertion path); acceptable.
- tests/bug-2962-windows-sdk-shim.test.cjs:132 — Date.now() + 20 (20 ms
relative offset used in a deadline expiry calculation, not a spin loop).
- tests/core.test.cjs:2006 — timeAgo(new Date(Date.now() + 5000)) (a
value assertion for the timeAgo utility, not a polling loop).
No additional assertion-path wall-clock polls found.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>