Skip to content

Fix race condition in ProcessBufferedRenderBatches_WritesRenders#65783

Merged
lewing merged 3 commits into
mainfrom
fix/remote-renderer-test-race
Mar 16, 2026
Merged

Fix race condition in ProcessBufferedRenderBatches_WritesRenders#65783
lewing merged 3 commits into
mainfrom
fix/remote-renderer-test-race

Conversation

@lewing
Copy link
Copy Markdown
Member

@lewing lewing commented Mar 15, 2026

Problem

ProcessBufferedRenderBatches_WritesRenders fails intermittently in CI (#61807), quarantined since Sep 2025.

Assert.True() Failure
Expected: True
Actual:   False
  at RemoteRendererTest.TestComponent.TriggerRender() in RemoteRendererTest.cs:line 778

Root Cause

The test had a ManualResetEventSlim that was never signaled — it was created, reset, and waited on with a 10-second timeout, but nothing ever called Set(). The comment acknowledged that "continuations of SetResult will not execute synchronously" but the synchronization mechanism was broken.

After firstBatchTCS.SetResult(), the renderer processes the batch completion asynchronously on its dispatcher. The test proceeded after the 10s timeout expired, then called TriggerRender() which asserts the render task completes synchronously. On loaded CI agents, the renderer's dispatcher was still busy processing the previous batch's continuation, causing the assertion to fail.

Fix

Move the post-SetResult() test setup (SetDisconnected + TriggerRender calls) inside a renderer.Dispatcher.InvokeAsync callback. This ensures:

  1. All prior dispatcher work (from SetResult continuations) drains before our callback executes
  2. TriggerRender runs with CheckAccess() == true, bypassing the internal task queue and executing the render synchronously

A single await InvokeAsync(() => {}) no-op was insufficient because InvokeAsync(Action) returns a task that completes inside PostAsync's callback, but PostAsync's own task (stored as _taskQueue) completes slightly later — creating a race where the next TriggerRender would chain instead of running synchronously.

Related to #61807

@lewing lewing requested a review from a team as a code owner March 15, 2026 03:17
Copilot AI review requested due to automatic review settings March 15, 2026 03:17
@github-actions github-actions Bot added the area-blazor Includes: Blazor, Razor Components label Mar 15, 2026
@lewing lewing force-pushed the fix/remote-renderer-test-race branch from 7cb7ae2 to d1a68f9 Compare March 15, 2026 03:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 re-enables and stabilizes the previously quarantined ProcessBufferedRenderBatches_WritesRenders test by changing how it waits for the renderer to finish processing the first render batch, aiming to eliminate intermittent CI failures in the Components Server circuit rendering tests.

Changes:

  • Removes the [QuarantinedTest] attribute from ProcessBufferedRenderBatches_WritesRenders so it runs in CI again.
  • Replaces an unused ManualResetEventSlim wait with a timeout-bounded loop intended to wait for render-batch processing to complete.

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/Components/Server/test/Circuits/RemoteRendererTest.cs Outdated
Comment thread src/Components/Server/test/Circuits/RemoteRendererTest.cs Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 fixes an intermittent CI failure in the Components Server RemoteRendererTest by replacing a broken synchronization mechanism with a dispatcher-queued no-op, and re-enables the previously quarantined test.

Changes:

  • Remove the quarantine marker from ProcessBufferedRenderBatches_WritesRenders so it runs in CI again.
  • Replace the unused/unsignaled ManualResetEventSlim wait with await renderer.Dispatcher.InvokeAsync(() => { }) to correctly sequence dispatcher work after SetResult().

You can also share your feedback on Copilot code review. Take the survey.

Comment thread src/Components/Server/test/Circuits/RemoteRendererTest.cs
Comment thread src/Components/Server/test/Circuits/RemoteRendererTest.cs Outdated
The test used a ManualResetEventSlim that was never signaled, relying
on a 10-second timeout to elapse before proceeding. This masked a race
where firstBatchTCS.SetResult() triggers an async continuation in the
renderer, but TriggerRender() was called before that continuation
completed. On loaded CI agents, TriggerRender's assertion that the
render task completes synchronously would fail because the renderer's
dispatcher was still busy processing the previous batch.

Replace the broken event wait with polling on the renderer's observable
state (_unacknowledgedRenderBatches.Count) to ensure the first batch
is fully processed before triggering more renders.

Fixes #61807

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous fix used a no-op dispatcher drain followed by TriggerRender()
from the test thread. This had a race: InvokeAsync(Action) returns a task
(t) that completes inside PostAsync's callback, but PostAsync's own task
(stored as _taskQueue) completes slightly later. When the test's await
resumed, _taskQueue could still be non-completed, causing the next
TriggerRender's InvokeAsync to chain instead of running synchronously.

Fix by running TriggerRender inside the dispatcher callback. When
CheckAccess() is true, Dispatcher.InvokeAsync bypasses _taskQueue
entirely and runs the action synchronously, returning Task.CompletedTask.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lewing lewing requested a review from oroztocil March 15, 2026 16:26
@lewing lewing marked this pull request as ready for review March 15, 2026 16:46
Copy link
Copy Markdown
Member

@ilonatommy ilonatommy left a comment

Choose a reason for hiding this comment

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

The fix looks fine.

Also removes the quarantine attribute so the test runs in CI again.

The process is to keep it in quarantined. We should merge this PR and add test-fixed label to the original issue (that we keep open - remove fixes keyword from the PR description). During the CI duty these tests that have been passing for a month will get moved from the quarantine.

Per review feedback: the test should remain quarantined and the
original issue should get the 'test-fixed' label. After passing
in quarantine CI for a month, it will be moved out.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@lewing lewing requested a review from ilonatommy March 16, 2026 17:53
@akoeplinger akoeplinger dismissed ilonatommy’s stale review March 16, 2026 23:27

feedback adressed

@lewing lewing merged commit ab2e33a into main Mar 16, 2026
25 checks passed
@lewing lewing deleted the fix/remote-renderer-test-race branch March 16, 2026 23:28
@dotnet-policy-service dotnet-policy-service Bot added this to the 11.0-preview3 milestone Mar 16, 2026
@ilonatommy
Copy link
Copy Markdown
Member

ilonatommy commented Mar 17, 2026

Fun fact, copilot decided to add "fixes" keyword to squash message anyway because it s created by concatenating all commit messages of the PR and the 1st commit contained it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-blazor Includes: Blazor, Razor Components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants