Prevent Static SSR Stack Overflow During Section Updates - #69110
Prevent Static SSR Stack Overflow During Section Updates#69110PreethikaSelvam wants to merge 4 commits into
Conversation
|
Thanks for your PR, @PreethikaSelvam. Someone from the team will get assigned to your PR shortly and we'll get it reviewed. |
There was a problem hiding this comment.
🟡 Changes recommended
The new render-queue deferral APIs are internal in Renderer, but they’re used by StaticHtmlRenderer across assemblies via inheritance, which will break compilation unless the methods are made protected (or protected internal).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR addresses a Blazor static SSR failure mode where re-entrant section updates (for example, PageTitle via SectionOutlet) can invalidate render-tree frame buffers while StaticHtmlRenderer is synchronously walking them to write HTML, potentially leading to recursive cycles and a process-terminating StackOverflowException. The fix introduces render-queue deferral around the synchronous HTML walk and adds unit tests to cover replacing/removing section content mid-write and preserving writer exceptions.
Changes:
- Defer render-queue processing while
StaticHtmlRenderer.WriteComponentHtmlsynchronously traverses render-tree frames, and flush deferred work after the outermost successful write. - Add render-queue deferral depth tracking in
Rendererto prevent re-entrant render-queue processing during HTML serialization. - Add unit tests to reproduce/guard section replacement, section removal, and writer-exception preservation scenarios.
File summaries
| File | Description |
|---|---|
| src/Components/Web/test/HtmlRendering/HtmlRendererTest.cs | Adds regression tests that mutate section content during HTML writing and verify correct deferral/exception behavior. |
| src/Components/Web/src/HtmlRendering/StaticHtmlRenderer.HtmlWriting.cs | Wraps the synchronous HTML walk in render-queue deferral and ensures deferred work is processed only after a successful outer write. |
| src/Components/Components/src/RenderTree/Renderer.cs | Adds deferral depth tracking and suppresses render-queue processing while deferral is active. |
Review details
Suppressed comments (1)
src/Components/Components/src/RenderTree/Renderer.cs:855
- EndRenderQueueDeferral is declared as internal, but it’s called from StaticHtmlRenderer (in a different assembly) via inheritance. This needs to be protected (or protected internal) so derived renderers outside this assembly can compile.
internal void EndRenderQueueDeferral(bool processPendingRender)
{
Dispatcher.AssertAccess();
Debug.Assert(_renderQueueDeferralDepth > 0);
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
🟡 Changes recommended
The new test helper code introduces nullable-reference-type mismatches (non-nullable members assigned/overridden with nullable signatures), which will likely fail builds with warnings-as-errors.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (1)
src/Components/Web/test/HtmlRendering/HtmlRendererTest.cs:1469
StringWriter.Write(string)accepts a nullable string (string?). This override usesstringand will trigger nullability-mismatch warnings (often treated as errors).
public override void Write(string value)
=> throw new IOException("Writing failed.");
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Lite
|
@PreethikaSelvam do you have a repro for this? |
Hi @javiercn, I don’t have a repro for the exact intermittent Linux CI scenario. I added a deterministic unit test for a similar scenario that reproduces the underlying reentrant section-update hazard during StaticHtmlRenderer’s HTML walk. It fails before the fix when the walk reaches disposed component state and passes afterward. |
|
@PreethikaSelvam where do you get the CI failure from? The report is not from our CI, isn't it? |
@javiercn, based on the Linux CI failure reported from the issue reporter’s environment I have created a deterministic unit test for a similar scenario that exercises the same underlying reentrant section-update hazard, and implemented the fix based on that test. We could not reproduce the exact intermittent scenario described in 69035. |
|
This is not a valid regression test for #69035 because the test itself creates the condition we need to explain. After both roots reach quiescence, This is different from an asynchronous lifecycle operation completing during serialization. The HTML walk runs synchronously on the renderer’s synchronization context. A normal lifecycle continuation dispatched from another thread cannot interrupt that walk; it must wait until the current synchronous work returns. The test bypasses that scheduling boundary by invoking the mutation directly. Consequently, the red/green result only demonstrates that queue deferral handles the reentrancy injected by the test. It does not establish a production path to the reported failure, demonstrate the reported component cycle, or establish its root cause. We should not introduce renderer-wide queue deferral and change rendering/removal timing to accommodate an artificial reproduction. We first need a minimal reproducer using normal components, lifecycle operations, and the endpoint rendering path, without a writer or renderer override injecting mutations during serialization. Until that producer is identified, this change is not justified as a fix for #69035. |
Bug Description
This PR fixes an issue where Blazor static SSR can encounter a process-terminating stack overflow when section content, such as PageTitle, changes while StaticHtmlRenderer is writing HTML.
Fixes #69035
Problem
While StaticHtmlRenderer synchronously walks render-tree frames, a reentrant section update can replace and dispose the current SectionOutletContentRenderer.
The active HTML walk can then continue through stale render-tree frames, potentially creating a recursive component cycle and terminating the process with a StackOverflowException.
Root Cause
Dispatcher access prevents concurrent rendering, but it does not prevent synchronous reentrant rendering.
A section update during HTML serialization can call:
AddToRenderQueue → ProcessPendingRender → ProcessRenderQueue
This mutates or recycles render-tree frame buffers while the renderer is still traversing them.
Solution Description
Added nested render-queue deferral while StaticHtmlRenderer writes component HTML.
The fix:
Updates queued during an HTML walk are applied to the next HTML write.
Testing
Added three unit tests covering:
The root-removal regression failed before the fix with:
The renderer does not have a component with ID 4.
Validation results:
The exact intermittent Linux CI stack overflow could not be reproduced locally on Windows. However, the underlying reentrant render-tree mutation and disposed-component failure were reproduced deterministically in unit tests, including red/green verification.
With Fix:


Without Fix:
Impact