Fix flaky deadline tests on coarse-resolution timers - #634
Conversation
The nightly build failed on `exec::budget::tests::deadline_trips_when_elapsed`
with `left: Ok(())`, `right: Err(Deadline { limit_secs: 0 })`.
Root cause: the deadline trips when `started.elapsed() > limit`. With a
zero-second `max_duration`, that requires the monotonic clock to have advanced
at least one tick past creation. `Instant`'s resolution is coarse on some
platforms (notably the Windows nightly runner), so the first `charge_operation`
could land within the same tick and read `elapsed() == 0`, making `0 > 0` false
and returning `Ok(())` instead of the expected deadline error.
Fix is test-only and leaves the production `>` ("trip once you exceed") deadline
semantics unchanged: spin until `elapsed()` moves off zero before asserting, so
the zero-second deadline is genuinely elapsed. Apply the same guard to the
sibling `pattern_meter_deadline_exemption_is_read_live`, which shares the latent
timing dependency.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S8qrho14PuMRudxgh1hVUm
|
Warning Review limit reached
Next review available in: 48 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. 📝 WalkthroughWalkthroughTwo budget unit tests now wait for elapsed time to become non-zero before asserting zero-second deadline behavior, avoiding timer-resolution races. ChangesBudget deadline tests
Estimated code review effort: 1 (Trivial) | ~2 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
This PR hardens execution-budget deadline tests against coarse std::time::Instant resolution (notably on Windows) by ensuring the clock has advanced before asserting that a 0-second deadline has elapsed.
Changes:
- Added a precondition wait in
deadline_trips_when_elapsedsoelapsed() > 0before asserting deadline exceedance. - Added the same wait in
pattern_meter_deadline_exemption_is_read_liveto make the post-main-loop assertion deterministic.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| while budget.elapsed() == Duration::ZERO { | ||
| std::hint::spin_loop(); | ||
| } |
| while budget.elapsed() == Duration::ZERO { | ||
| std::hint::spin_loop(); | ||
| } |
| // See `deadline_trips_when_elapsed`: the zero-second deadline only trips | ||
| // once the monotonic clock has moved past creation. Wait for the first | ||
| // tick so the post-main-loop assertion below is deterministic on | ||
| // coarse-resolution timers (e.g. Windows). |
Address Copilot review on #634: the spin-wait that lets the monotonic clock advance past a zero-second deadline was unbounded, so a pathologically frozen clock could hang the test suite instead of failing. Factor the wait into a shared `wait_for_clock_to_advance` helper bounded by an iteration cap (not a wall-clock timeout, which would depend on the very clock under suspicion). If the clock never advances it panics with a clear message rather than deadlocking CI. Both `deadline_trips_when_elapsed` and `pattern_meter_deadline_exemption_is_read_live` now use it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S8qrho14PuMRudxgh1hVUm
|
Thanks for the review — all three points are addressed in
Production deadline semantics ( Posted by the WFL repo warden (automated triage pass). Generated by Claude Code |
| fn wait_for_clock_to_advance(budget: &ExecutionBudget) { | ||
| const MAX_SPINS: u64 = 100_000_000; | ||
| for _ in 0..MAX_SPINS { | ||
| if budget.elapsed() != Duration::ZERO { | ||
| return; | ||
| } | ||
| std::hint::spin_loop(); | ||
| } |
Address Copilot review on #634: the bounded wait was a tight busy-wait that could monopolise a core until the next timer tick (~15ms on Windows) on a loaded CI runner. Yield to the scheduler every 1024 iterations instead of spinning every one; this stays deterministic, is friendlier under load, and lets the wall clock advance sooner so the wait usually exits earlier. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S8qrho14PuMRudxgh1hVUm
|
Addressed in Posted by the WFL repo warden (automated triage pass). Generated by Claude Code |
Companion to #634, which deflakes the two zero-second-deadline tests in `src/exec/budget.rs`. The same latent race exists in the integration suite: `checked_lexer_reports_expired_deadline_on_a_short_input` lexes an input too short to reach a strided checkpoint, so the entry checkpoint's `check_deadline()` is the only deadline read. That check is `started.elapsed() > limit`, which with a zero-second limit only trips once the monotonic clock has strictly advanced — and `Instant::elapsed()` can return exactly `Duration::ZERO` when both reads land in the same tick. Wait (bounded, iteration-capped) for the clock to advance before lexing, mirroring the helper #634 adds. Test-only; runtime semantics unchanged.
|
Confirming this diagnosis independently from an automated maintainer pass — and flagging one sibling test it doesn't reach. Agreed on the root cause. Strong agreement on leaving One gap: there's a third test in the same family that this PR doesn't cover —
Posted by the WFL repo warden (automated triage pass). |
|
CI is now fully green here (15 pass, 1 skipping, PR CI never runs
So Windows clippy and Windows That same blind spot explains both of this week's nightly breaks: the 07-17 red was Incidentally this is why #635 is worth keeping as a companion: its test lives in Posted by the WFL repo warden (automated triage pass). |
…ers (#635) Companion to #634, which deflakes the two zero-second-deadline tests in `src/exec/budget.rs`. The same latent race exists in the integration suite: `checked_lexer_reports_expired_deadline_on_a_short_input` lexes an input too short to reach a strided checkpoint, so the entry checkpoint's `check_deadline()` is the only deadline read. That check is `started.elapsed() > limit`, which with a zero-second limit only trips once the monotonic clock has strictly advanced — and `Instant::elapsed()` can return exactly `Duration::ZERO` when both reads land in the same tick. Wait (bounded, iteration-capped) for the clock to advance before lexing, mirroring the helper #634 adds. Test-only; runtime semantics unchanged. Co-authored-by: WFL Repo Warden <warden@starnet.local>
Summary
Fix race conditions in execution-budget deadline tests on platforms with coarse timer resolution (notably Windows). The tests were non-deterministic because they assumed the monotonic clock would advance between budget creation and the first deadline check, but on some platforms the clock may not tick within that window — so
elapsed()read0,0 > 0was false, and a zero-second deadline returnedOk(())instead of the expectedErr(Deadline).This was the cause of the failing nightly build.
Changes
deadline_trips_when_elapsedtest: waits for the monotonic clock to advance past budget creation before asserting the deadline has been exceeded, ensuringelapsed() > 0before the assertion.pattern_meter_deadline_exemption_is_read_livetest: applies the same wait before entering the main-loop boundary, so the post-loop deadline assertion is deterministic on coarse-resolution timers.Shared
wait_for_clock_to_advancehelper: both tests share one bounded wait. It is capped by an iteration count (not a wall-clock timeout, which would depend on the very clock under suspicion), so a pathologically frozen clock fails loudly with a clear panic rather than hanging the suite.Implementation Details
The helper spins with
std::hint::spin_loop()whilebudget.elapsed() == Duration::ZERO, up to a fixed iteration cap. This approach:sleep()) that would slow the tests.elapsed() > limit) untouched — this is a test-only change, so existing WFL program behavior is unaffected.Verification
cargo fmt --all -- --check,cargo clippy --all-targets --all-features -- -D warnings, andcargo test --lib exec::budget(18/18) all pass locally. The original failure is a coarse-Windows-timer race not reproducible on Linux; the fix removes the timing dependency entirely.https://claude.ai/code/session_01S8qrho14PuMRudxgh1hVUm
``<img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open in Devin Review">``
Summary by CodeRabbit