Skip to content

test: make the event-loop-blocking guard causal instead of wall-clock - #203

Draft
lstein wants to merge 1 commit into
mainfrom
fix/deflake-event-loop-blocking-test
Draft

test: make the event-loop-blocking guard causal instead of wall-clock#203
lstein wants to merge 1 commit into
mainfrom
fix/deflake-event-loop-blocking-test

Conversation

@lstein

@lstein lstein commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

The flake

test_slow_gallery_read_leaves_the_event_loop_free failed transiently on #195's CI (py3.12 macOS): the probe request took 1.09s against a 0.5s threshold. That's the exact timing signature of a blocked event loop — but the loop was fine; a loaded runner descheduled the whole process for ~1s. Any wall-clock threshold makes machine noise and the guarded regression indistinguishable.

The fix

Make the assertion causal. The patched service now blocks on an Event that only the test releases — strictly after the probe completes:

  • Healthy def route: handler waits in the threadpool, probe answers, handler provably hasn't finished (it can't have — nothing released it), then the test releases it. No timing anywhere on the passing path.
  • async def regression: the loop is stuck inside the handler's wait; the probe can only be answered after a 30s backstop expires, by which point the handler has finished → the finished-flag assertion fires with the same actionable "declare the route def" message. The backstop is paid only when the guard actually catches a regression.

The test also waits (off-loop, via asyncio.to_thread) for the handler to have started before probing, so the probe is provably concurrent rather than hoping ten sleep(0)s sufficed.

Verified in both directions

  • Healthy: 6/6 pass in 0.24s (down from ~7s — the real 1s sleeps are gone too).
  • Regression: temporarily flipping /api/v1/gallery/items/ to async def fails deterministically in ~15s with the intended assertion, then the route was restored.

(#195 itself is separately unblocked: its conflict with main — both sides had re-recorded the webv2 performance baselines — is resolved by a merge commit that regenerates both baselines on the merged tree; all its checks were already green after the rerun.)

🤖 Generated with Claude Code

https://claude.ai/code/session_014xFbHnmFTLsHE9e1PAdvme

test_slow_gallery_read_leaves_the_event_loop_free asserted the probe
request finished in under BLOCKING_SECONDS/2 (0.5s) of wall time. A
descheduled CI runner fails that with a perfectly free event loop - a
loaded macOS runner froze the process ~1s and produced a 1.09s probe,
the exact timing signature of a blocked loop. Machine noise and the
regression being guarded against were indistinguishable.

Rework the simulation so the assertion is causal: the patched service
blocks on an Event only the TEST releases, and it is released strictly
AFTER the probe completes. On a healthy 'def' route the handler cannot
have finished when the probe returns; if the route regresses to 'async
def', the loop is stuck inside the handler's wait, the probe can only be
answered after the 30s backstop expires, and the finished-flag assertion
fires with the same actionable message. No wall-clock threshold remains
on the passing path.

Verified in both directions: 6/6 pass in 0.24s (down from ~7s - the real
1s sleeps are gone too), and temporarily regressing
/api/v1/gallery/items/ to 'async def' fails deterministically in ~15s
with the intended assertion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014xFbHnmFTLsHE9e1PAdvme
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant