Repository navigation
Make Phase0 service tests deterministic under parallel load - #107
Merged
Merged
Conversation
The Phase0 query, request-queue and telemetry tests used 1-10 s wall-clock timeouts as hang guards. On a loaded CI runner running the whole suite in parallel, those guards tripped even though the code under test was correct. - Add AsyncTestWait with a 30 s HangGuard and a polling UntilAsync helper; replace the per-class WaitForAsync/WaitUntilAsync helpers and the short WaitAsync/Wait timeouts in Phase0 tests with it. - RefreshAsync_AccountCleanupCancelsDelayedCachePutBeforePartitionClear: read the empty partition first, which checks the precondition the final assertion relies on and moves cold SQLite schema creation out of the window being raced. Guard the refresh await so a cancellation regression fails instead of hanging. - RequestQueue_LastPrefetchSubscriberCancellationStopsActiveWork: signal from the work's own cancellation path instead of a token.Register callback scoped to the work, which the resumed work could unregister before the token ran it. Assert the work token is cancelled once the caller observes cancellation, which the queue guarantees. - RequestQueue_ForegroundLaneIsNeverBlockedByQueuedPrefetchWork: replace WhenAny with a 2 s Delay by awaiting the foreground with the hang guard; the background lane stays held, so a blocked foreground still fails. No production code changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zhuowcui
approved these changes
Oct 28, 2026
zhuowcui
approved these changes
Oct 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Two app tests kept timing out when the full
JitHub.WinUI.Testssuite ran in parallel on a busy machine, which blocked unrelated PRs (for example the MarkdownRenderer stack, #106). Both passed every time when run alone. The cause was short wall-clock timeouts used as hang guards: they measured machine load, not correctness. This PR changes only test code. I found no race in the code under test.Changes
New
TestSupport/AsyncTestWaitHangGuard(30 s): used only to stop a hung test. It never decides whether timing-sensitive behavior passed.UntilAsync(condition): polls until a condition holds and fails with a message afterHangGuard. It replaces the per-classWaitForAsync(1 s budget) andWaitUntilAsync(2 s budget) helpers.Phase0QueryServiceTests.RefreshAsync_AccountCleanupCancelsDelayedCachePutBeforePartitionClearTimeoutExceptionat line 361 (PutStarted.WaitAsync(2s)) in run 40635407090, attempt 1.Task.Run, and the first call creates the SQLite schema on disk. On a loaded 2-core runner, that path alone can take more than 2 s.Assert.Nullrelies on, and it moves cold schema creation out of the window being raced. The waits useHangGuard.refreshis now awaited withHangGuard. If cleanup ever stopped cancelling the write, the test fails with "expected OperationCanceledException, got TimeoutException" instead of hanging. Checked by pointing the shared work at_sharedWorkCancellation.Tokeninstead of the account lease token: the test fails as described.Phase0RequestTransportTests.RequestQueue_LastPrefetchSubscriberCancellationStopsActiveWorktransportCancelled.SetResultwithtoken.Registerinside ausing, then awaitedTask.Delay(token). Cancellation callbacks run newest-first, so the delay's callback runs before the signal's. The delayed work resumes on another thread and disposes the registration. If that happens before the token reaches the older callback, the signal is lost and the test waits out its timeout. The window is small: 0 lost in 200,000 tries in a standalone repro. The 10 s guard could also trip on thread-pool starvation alone.catch (OperationCanceledException) when (token.IsCancellationRequested), which cannot be lost.workToken.IsCancellationRequested. This holds by construction: the caller's task only completes after the queue drops its last subscriber, and dropping the last subscriber cancels the shared token.EnqueueCoreAsyncdisposes its registration, which waits for a runningRemoveSubscribercallback._sharedWorkCancellation.Cancel()fromRemoveSubscriber: the test fails in 18 ms on that assertion. Before this PR, the same mutation would have meant a 10 s timeout.Other Phase0 waits with the same shape
RequestQueue_ForegroundLaneIsNeverBlockedByQueuedPrefetchWork:Task.WhenAny(foreground, Task.Delay(2s))is nowforeground.WaitAsync(HangGuard). The background lane stays held until the test releases it, so a foreground read stuck behind background work still never completes and still fails the test.WaitAsync,WaitandWaitForIdleAsyncinPhase0QueryServiceTests,Phase0RequestTransportTestsandPhase0TelemetryTestsis a wait for a signal the code must raise. Each now usesHangGuard.Task.Delay(5)ordering gaps.All
[Trait("Category", "ReleaseSecurity")]attributes are unchanged.Verification
dotnet test JitHub.WinUI.Tests/JitHub.WinUI.Tests.csproj -c Debug -p:Platform=x64, 20 runs in a row: every Phase0 test passed in all 20, with a concurrent Release build adding load for part of the loop. In 2 of the 20 runs, a different, pre-existing test failed:RepoCodePageViewModelTests.Initialize_CachedTreeRefreshReloadsReadmeWhenShaChanges, withExpected "readme-new", Actual "readme-old". This PR doesn't touch it.dotnet test ... -c Release --filter Category=ReleaseSecurity, passed (93 tests).dotnet build JitHub.WinUI -c Release -p:Platform=x64could not complete locally. The dev machine only has SDK 10.0.402, which brings ILCompiler 10.0.13. The lock files pin 10.0.12 (from theglobal.json10.0.2xx band), so the gate'sVerify-DependencySecurity.ps1locked restore fails here, and would onmaintoo. With locked restore relaxed, the app compiles in Release. CI's publish job runs the full gate with the pinned SDK.Follow-ups (not in this PR)
RepoCodePageViewModelTests.Initialize_CachedTreeRefreshReloadsReadmeWhenShaChangesis flaky under the full suite (2 in 20 runs), as described above.LatestWinsHoverStorm_DoesNotAccumulateQueuedNetworkRequestsrelies on a real 30 ms debounce. If the test thread stalls for longer than that while scheduling, an earlier item starts. It runs in the non-parallel collection, so the risk is low. A deterministic fix needs aTimeProviderseam inLatestWinsPrefetchScheduler, which is a production change.🤖 Generated with Claude Code