Skip to content

Drive LatestWinsPrefetchScheduler debounce from an injected clock - #114

Merged
Xueyang-Song merged 1 commit into
mainfrom
claude/quirky-chaum-158e7c
Oct 31, 2026
Merged

Xueyang-Song merged 1 commit into
mainfrom
claude/quirky-chaum-158e7c

Conversation

@Xueyang-Song

Copy link
Copy Markdown
Collaborator

Problem

AdaptivePrefetchPolicyTests.LatestWinsScheduler_HoverStormStartsOnlyFinalPrediction scheduled 100 predictions with a 30 ms debounce, slept 150 ms of real time, and then expected exactly one start. On a loaded runner the timer fired after the sleep and the test failed with Expected: 1, Actual: 0. Seen in run 41157408754, job "Native AOT contract and unit tests". The flake is on main and unrelated to the MarkdownRenderer work.

Change

  • Production: LatestWinsPrefetchScheduler takes a TimeProvider. The parameterless constructor, which every view model uses, passes TimeProvider.System. The debounce is now Task.Delay(debounce, _timeProvider, token), which for TimeProvider.System uses the same timer-queue path as the old Task.Delay(debounce, token). App behaviour is unchanged.

  • Tests: these three tests now drive a FakeTimeProvider and no longer sleep on real time:

    • LatestWinsScheduler_HoverStormStartsOnlyFinalPrediction
    • LatestWinsScheduler_RouteDepartureCancelsPendingAndActivePrediction
    • Phase0RequestTransportTests.LatestWinsHoverStorm_DoesNotAccumulateQueuedNetworkRequests

    The third test had the same real 30 ms debounce. A stall during its 100-item loop could let an early item start.

  • TimerCountingTimeProvider (in TestSupport) counts debounce timers as they fire. Advance runs timer callbacks on the calling thread, but code awaiting Task.Delay on a fake clock resumes later on the thread pool. "Only the final prediction's debounce elapsed" and "the cancelled debounce never elapsed" are therefore asserted on that count, which is exact when Advance returns. Waits for a positive signal use AsyncTestWait.HangGuard.

  • Package: Microsoft.Extensions.TimeProvider.Testing 10.10.0 is added to the test project. It has no dependencies on net10.0, and packages.lock.json gains only that entry. A locked restore passes.

No assertion was weakened. The hover-storm tests now also check that nothing fires 1 ms before the debounce.

Verification

  • Mutation checks: removing CancelSafely(supersededPending) fails both hover-storm tests. Removing CancelSafely(pending) in Cancel() fails the route-departure test. Both fail every time.
  • 50 of 50 runs of the LatestWins tests passed. They ran while the full suite looped in parallel and busy loops loaded all 24 cores.
  • The full suite passed 11 of 12 runs under that load. The one failure was an existing timing race in VNextShellHomeServiceTests.PullRequestNavigationCache_CancelledScheduledPrefetchDoesNotFetch (expected Cancelled, got Success). That test covers PullRequestNavigationCache, which does not use this scheduler. It is left for a separate fix.
  • JitHub.WinUI builds in Debug x64.

🤖 Generated with Claude Code

LatestWinsScheduler_HoverStormStartsOnlyFinalPrediction slept 150 ms on
real time and expected a 30 ms debounce to have fired. On a loaded CI
runner (run 41157408754) the timer fired late and the test saw 0 starts.

The scheduler now takes a TimeProvider (TimeProvider.System by default,
so the app's timer behaviour is unchanged) and its tests drive a
FakeTimeProvider instead of sleeping:

- Both LatestWinsScheduler_* tests and the transport-level
  LatestWinsHoverStorm test, which had the same wall-clock debounce,
  advance the clock explicitly.
- TimerCountingTimeProvider counts debounce timers as they fire. Advance
  runs timer callbacks inline, but the code after Task.Delay resumes on
  the thread pool, so "nothing else started" is asserted on the fired
  count, which is exact when Advance returns.
- Mutation checks: removing the cancel of a superseded prediction fails
  both hover-storm tests, and removing the cancel in Cancel() fails the
  route-departure test.

Adds Microsoft.Extensions.TimeProvider.Testing 10.10.0 to the test
project; the lock file gains only that entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Xueyang-Song
Xueyang-Song merged commit 3c39315 into main Oct 31, 2026
11 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants