Skip to content

Conversation

@javiercn
Copy link
Member

Summary

This change removes the need for synchronous buffering when writing SSR render batches to the HTTP response. The challenge was that we needed to write render batches synchronously because yielding the thread while writing to the output allows other continuations to be scheduled and modify contents between async boundaries.

Solution

Instead of buffering content synchronously and then writing it, we now:

  1. Move shared source files: RendererSynchronizationContext and RendererSynchronizationContextDispatcher are moved to src/Components/Shared/src/ so they can be compiled into both Components and Endpoints assemblies with different namespaces (via COMPONENTS_ENDPOINTS define).

  2. Add EnqueueBlockingTask(Task) method: This method inserts a blocking task into the sync context queue, preventing other continuations from running until it completes.

  3. EndpointHtmlRenderer creates its own dispatcher: This allows access to the Endpoints-specific RendererSynchronizationContext which has the EnqueueBlockingTask method.

  4. Add WriteContentAsync helper: In RazorComponentEndpointInvoker, this helper:

    • Creates a TaskCompletionSource with RunContinuationsAsynchronously
    • Enqueues the TCS.Task to block the sync context queue
    • Writes content asynchronously with ConfigureAwait(false) to avoid deadlock
    • Completes the TCS to unblock the queue

Benefits

  • Maintains thread safety (no other continuations can modify state while we write)
  • Allows async I/O operations instead of synchronous buffering
  • Reduces memory allocations from buffering

Testing

This change removes the need for synchronous buffering when writing SSR render batches to the HTTP response. Instead of buffering content synchronously and then writing it, we now:

1. Move RendererSynchronizationContext and RendererSynchronizationContextDispatcher to shared source (src/Components/Shared/src/) so they can be compiled into both Components and Endpoints assemblies with different namespaces.

2. Add EnqueueBlockingTask(Task) method to RendererSynchronizationContext that inserts a blocking task into the queue, preventing other continuations from running until it completes.

3. Have EndpointHtmlRenderer create its own dispatcher instance so it can access the Endpoints-specific RendererSynchronizationContext.

4. Add WriteContentAsync helper in RazorComponentEndpointInvoker that:
   - Creates a TaskCompletionSource with RunContinuationsAsynchronously
   - Enqueues the TCS.Task to block the sync context queue
   - Writes content asynchronously with ConfigureAwait(false)
   - Completes the TCS to unblock the queue

This approach maintains thread safety (no other continuations can modify state while we write) while allowing async I/O operations.
@github-actions github-actions bot added the area-blazor Includes: Blazor, Razor Components label Jan 23, 2026
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.

2 participants