Skip to content

Prevent Static SSR Stack Overflow During Section Updates - #69110

Open
PreethikaSelvam wants to merge 4 commits into
dotnet:mainfrom
PreethikaSelvam:GH69035Validation
Open

Prevent Static SSR Stack Overflow During Section Updates#69110
PreethikaSelvam wants to merge 4 commits into
dotnet:mainfrom
PreethikaSelvam:GH69035Validation

Conversation

@PreethikaSelvam

@PreethikaSelvam PreethikaSelvam commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

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:

  • Keeps render-tree frame buffers stable throughout the synchronous HTML walk
  • Supports recursive child-component HTML writes through a deferral-depth counter
  • Processes queued work after the outermost successful write
  • Defers root-component removal during the walk
  • Avoids starting a nested batch when one is already active
  • Preserves the original writer exception when HTML output fails

Updates queued during an HTML walk are applied to the next HTML write.

Testing

Added three unit tests covering:

  • Replacing section content while its outlet is being rendered
  • Removing section content while its outlet is being rendered
  • Preserving writer exceptions while retaining deferred rendering work

The root-removal regression failed before the fix with:
The renderer does not have a component with ID 4.
Validation results:

  • Components: 1,299 passed, 8 skipped
  • Components.Web: 316 passed
  • Components.Endpoints: 876 passed

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:
image
Without Fix:
image

Impact

  • Prevents reentrant rendering from invalidating static SSR frame traversal
  • Prevents stale section frames from producing recursive component cycles
  • Preserves existing renderer-specific queue behavior
  • Introduces no public API changes
  • Changes reentrant updates to appear on the next HTML write instead of the active write

@github-actions github-actions Bot added the area-blazor Includes: Blazor, Razor Components label Sep 8, 2026
@dotnet-policy-service dotnet-policy-service Bot added the community-contribution Indicates that the PR has been added by a community member label Sep 8, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Thanks for your PR, @PreethikaSelvam. Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

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.

🟡 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.WriteComponentHtml synchronously traverses render-tree frames, and flush deferred work after the outermost successful write.
  • Add render-queue deferral depth tracking in Renderer to 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.

Comment thread src/Components/Components/src/RenderTree/Renderer.cs

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.

🟡 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 uses string and 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

Comment thread src/Components/Web/test/HtmlRendering/HtmlRendererTest.cs
@PreethikaSelvam
PreethikaSelvam marked this pull request as ready for review September 8, 2026 09:02
@PreethikaSelvam
PreethikaSelvam requested a review from a team as a code owner September 8, 2026 09:02
@javiercn

javiercn commented Sep 8, 2026

Copy link
Copy Markdown
Member

@PreethikaSelvam do you have a repro for this?

@PreethikaSelvam

Copy link
Copy Markdown
Contributor Author

@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.

@javiercn

javiercn commented Sep 8, 2026

Copy link
Copy Markdown
Member

@PreethikaSelvam where do you get the CI failure from? The report is not from our CI, isn't it?

@PreethikaSelvam

Copy link
Copy Markdown
Contributor Author

@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.

@javiercn

javiercn commented Sep 8, 2026

Copy link
Copy Markdown
Member

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, BeforeRenderingSectionContent directly invokes an update or root removal from inside RenderChildComponent. That deliberately introduces render-tree mutation on the HTML walk’s own call stack.

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.

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 community-contribution Indicates that the PR has been added by a community member

Projects

None yet

4 participants