Skip to content

Fix flaky StampedeTests and harden related test waits - #7572

Merged
jeffhandley merged 3 commits into
dotnet:mainfrom
jeffhandley:jeffhandley/ci-unstable-test
Jun 15, 2026
Merged

jeffhandley merged 3 commits into
dotnet:mainfrom
jeffhandley:jeffhandley/ci-unstable-test

Conversation

@jeffhandley

@jeffhandley jeffhandley commented Jun 15, 2026 •

Copy link
Copy Markdown
Member

Fixes #6836

StampedeTests.MultipleCallsShareExecution_MostCancel (and its sibling _EveryoneCancels) asserted result.Status == TaskStatus.Canceled after a fixed Task.Delay(500). On loaded CI agents, the HybridCache stampede coordinator hadn't always transitioned every caller task to Canceled within that window, producing intermittent failures.

This PR replaces that pure-delay assertion with a bounded poll on the caller's task and opportunistically applies the same hardening to two other tests that used fixed delays before asserting on background-thread state.

StampedeTests (the actual flake fix)

  • Adds a private static WaitForCompletionAsync(Task, int timeoutMs, [CallerArgumentExpression] string?) helper modeled on AssertAwaitingTaskCompleted in FakeLogCollectorTests.LogEnumeration.cs. The timeoutMs has no default; the 5-second budget is passed explicitly at each call site.
  • Both MultipleCallsShareExecution_EveryoneCancels and _MostCancel now await WaitForCompletionAsync(result, 5_000) before asserting TaskStatus.Canceled.
  • The original 500ms Task.Delay warmup is retained with an updated comment explaining its actual purpose: letting the shared underlying lambda enter its semaphore.WaitAsync(...) before the main thread completes the handshake. A first attempt that removed it entirely failed 25/25 on net462 at Assert.Equal(1, executeCount) because the test reuses a single SemaphoreSlim for handshakes in both directions and SemaphoreSlim does not guarantee FIFO ordering.

Stress-verified locally: 25/25 pass on net8/9/10/462 for both methods after the fix.

Opportunistic improvements to related "pure delay before assert" tests

  • RedisTests: replaced await Task.Delay(500) before reading KeyTimeToLiveAsync with a bounded poll (50 attempts of 100ms = 5 seconds total) on the TTL becoming non-null. The test is IClassFixture<RedisFixture> and skipped without a Redis instance, so it is not in default CI -- this is purely defense-in-depth for the codebase's reusable wait pattern.
  • FakeHostTests:
    • Host_ShutsDownAfterTimeout: replaced a fixed delay with a try/catch poll (50 x 100ms = 5 seconds total) capturing ObjectDisposedException, then asserting it surfaced.
    • StartAsync_NoTokenProvided_UsesDefaultTimeout: replaced a fixed delay with a poll on hostMock.Invocations.Count (50 x 100ms = 5 seconds total) before hostMock.VerifyAll().

Stress-verified locally: 25/25 pass on net8/9/10/462 for both FakeHostTests methods.

Microsoft Reviewers: Open in CodeFlow
Microsoft Reviewers: Open in CodeFlow

jeffhandley and others added 3 commits June 15, 2026 13:25
Replaces the bare 'await Task.Delay(500)' before the cancelled-caller asserts in MultipleCallsShareExecution_EveryoneCancels and MultipleCallsShareExecution_MostCancel with a bounded WaitForCompletionAsync(results[i], 5_000) call. The 500ms warmup is retained because the shared underlying task needs that window to enter its semaphore wait before the test releases the semaphore for the handshake.

- Add private static WaitForCompletionAsync(Task, int timeoutMs) helper using Task.WhenAny(task, Task.Delay(timeoutMs)) with a CallerArgumentExpression-driven TimeoutException, suppressing VSTHRD003 to match the AssertAwaitingTaskCompleted helper in FakeLogCollectorTests.LogEnumeration.cs.

- In each test, await WaitForCompletionAsync(result, 5_000) immediately before Assert.Equal(TaskStatus.Canceled, result.Status) so the assertion no longer races the worker-thread cancellation transition.

Fixes dotnet#6836

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the bare 'await Task.Delay(500)' between the cache populate loop and the KeyTimeToLiveAsync assertion with a bounded poll that calls KeyTimeToLiveAsync up to 50 times with 100ms between attempts, exiting as soon as a non-null TTL is returned.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replaces the two bare 'await Task.Delay(100)' waits in FakeHostTests:

- Host_ShutsDownAfterTimeout: polls host.Services.GetService<IHost>() in a try/catch loop (up to 50 attempts of 100ms) and captures the ObjectDisposedException, asserting the exception was observed instead of asserting it on a single direct call.

- StartAsync_NoTokenProvided_UsesDefaultTimeout: polls hostMock.Invocations.Count (up to 50 attempts of 100ms) before calling hostMock.VerifyAll().

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 15, 2026 20:28
@jeffhandley
jeffhandley requested review from a team as code owners June 15, 2026 20:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens several timing-sensitive tests by replacing fixed Task.Delay(...) waits with bounded polling/timeout-based waiting, addressing CI flakiness in the HybridCache stampede cancellation tests (Issue #6836) and applying the same pattern to a couple of other tests that asserted on eventually-consistent background state.

Changes:

  • Updated StampedeTests cancellation scenarios to wait (with a timeout) for caller tasks to complete before asserting TaskStatus.Canceled, and introduced a local WaitForCompletionAsync helper.
  • Replaced a fixed delay in RedisTests with a bounded poll for a Redis TTL to become observable after an async L2 write.
  • Replaced fixed delays in FakeHostTests with bounded polling to observe host disposal and Moq invocation recording.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
test/Libraries/Microsoft.Extensions.Hosting.Testing.Tests/FakeHostTests.cs Replaces fixed delays with bounded polling to reduce timing-related flakiness in host shutdown and invocation assertions.
test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/StampedeTests.cs Fixes the reported flake by waiting for cancellation completion with a timeout before asserting task status; adds a helper for bounded waits.
test/Libraries/Microsoft.Extensions.Caching.Hybrid.Tests/RedisTests.cs Replaces a fixed delay with a bounded poll for TTL visibility after background Redis writes.

@dotnet-comment-bot

Copy link
Copy Markdown
Collaborator

‼️ Found issues ‼️

Project Coverage Type Expected Actual
Microsoft.Extensions.Diagnostics.Testing Line 99 98.65 🔻
Microsoft.Extensions.Telemetry Line 93 91.95 🔻
Microsoft.Extensions.AI Line 89 88.45 🔻
Microsoft.Extensions.AI Branch 89 88.57 🔻
Microsoft.Extensions.AI.OpenAI Line 75 62.65 🔻
Microsoft.Extensions.AI.OpenAI Branch 75 49.63 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Line 75 4.46 🔻
Microsoft.Extensions.DataIngestion.MarkItDown Branch 75 0 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Line 99 96.03 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring Branch 99 94.39 🔻
Microsoft.Extensions.Diagnostics.ResourceMonitoring.Kubernetes Line 99 97.73 🔻
Microsoft.Extensions.ServiceDiscovery.Dns Line 75 69.93 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Line 75 42.11 🔻
Microsoft.Extensions.ServiceDiscovery.Abstractions Branch 75 42.86 🔻
Microsoft.Extensions.ServiceDiscovery Line 75 67.81 🔻
Microsoft.Extensions.ServiceDiscovery Branch 75 71.43 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Line 75 73.85 🔻
Microsoft.Extensions.ServiceDiscovery.Yarp Branch 75 70 🔻
Microsoft.Extensions.VectorData.Abstractions Line 75 37.39 🔻
Microsoft.Extensions.VectorData.Abstractions Branch 75 22.73 🔻

🎉 Good job! The coverage increased 🎉
Update MinCodeCoverage in the project files.

Project Expected Actual
Microsoft.Gen.BuildMetadata 97 100
Microsoft.Gen.MetadataExtractor 57 73
Microsoft.Gen.MetricsReports 67 69
Microsoft.Extensions.AI.Abstractions 82 85
Microsoft.Extensions.AI.Evaluation.NLP 0 78
Microsoft.Extensions.Caching.Hybrid 82 84
Microsoft.Extensions.DataIngestion 75 89
Microsoft.Extensions.DataIngestion.Markdig 75 90
Microsoft.Extensions.Http.Resilience 97 100

Full code coverage report: https://dev.azure.com/dnceng-public/public/_build/results?buildId=1465264&view=codecoverage-tab

@jeffhandley
jeffhandley merged commit 3e77e3a into dotnet:main Jun 15, 2026
8 checks passed
@jeffhandley
jeffhandley deleted the jeffhandley/ci-unstable-test branch June 15, 2026 21:58
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 16, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Microsoft.Extensions.Caching.Hybrid.Tests.StampedeTests.MultipleCallsShareExecution_MostCancel(callerCount: 10, remaining: 0) CI failure

4 participants