Skip to content

Commit e588749

Browse files
Implement fix
1 parent 0c339d0 commit e588749

File tree

3 files changed

+21
-8
lines changed

3 files changed

+21
-8
lines changed

src/Components/Endpoints/src/RazorComponentEndpointInvoker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ await EndpointHtmlRenderer.InitializeStandardComponentServicesAsync(
128128
}
129129
else
130130
{
131-
await _renderer.EmitInitializersIfNecessary(context, bufferWriter);
131+
_renderer.EmitInitializersIfNecessary(context, bufferWriter);
132132
}
133133

134134
// Emit comment containing state.

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.Streaming.cs

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ public void InitializeStreamingRenderingFraming(HttpContext httpContext, bool is
3939

4040
public async Task SendStreamingUpdatesAsync(HttpContext httpContext, Task untilTaskCompleted, TextWriter writer)
4141
{
42+
// Important: do not introduce any 'await' statements in this method above the point where we write
43+
// the SSR framing markers, otherwise batches may be emitted before the framing makers, and then the
44+
// response would be invalid. See the comment below indicating the point where we intentionally yield
45+
// the sync context to allow SSR batches to begin being emitted.
46+
4247
SetHttpContext(httpContext);
4348

4449
if (_streamingUpdatesWriter is not null)
@@ -56,9 +61,11 @@ public async Task SendStreamingUpdatesAsync(HttpContext httpContext, Task untilT
5661

5762
try
5863
{
59-
await writer.WriteAsync(_ssrFramingCommentMarkup);
60-
await EmitInitializersIfNecessary(httpContext, writer);
61-
await writer.FlushAsync(); // Make sure the initial HTML was sent
64+
writer.Write(_ssrFramingCommentMarkup);
65+
EmitInitializersIfNecessary(httpContext, writer);
66+
67+
// At this point we yield the sync context. SSR batches may then be emitted at any time.
68+
await writer.FlushAsync();
6269
await untilTaskCompleted;
6370
}
6471
catch (NavigationException navigationException)
@@ -77,15 +84,15 @@ public async Task SendStreamingUpdatesAsync(HttpContext httpContext, Task untilT
7784
}
7885
}
7986

80-
internal async Task EmitInitializersIfNecessary(HttpContext httpContext, TextWriter writer)
87+
internal void EmitInitializersIfNecessary(HttpContext httpContext, TextWriter writer)
8188
{
8289
if (_options.JavaScriptInitializers != null &&
8390
!IsProgressivelyEnhancedNavigation(httpContext.Request))
8491
{
8592
var initializersBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(_options.JavaScriptInitializers));
86-
await writer.WriteAsync("<!--Blazor-Web-Initializers:");
87-
await writer.WriteAsync(initializersBase64);
88-
await writer.WriteAsync("-->");
93+
writer.Write("<!--Blazor-Web-Initializers:");
94+
writer.Write(initializersBase64);
95+
writer.Write("-->");
8996
}
9097
}
9198

src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ protected override Task UpdateDisplayAsync(in RenderBatch renderBatch)
139139

140140
if (_streamingUpdatesWriter is { } writer)
141141
{
142+
// Important: SendBatchAsStreamingUpdate *must* be invoked synchronously
143+
// before any 'await' in this method. That's enforced by the compiler
144+
// (the method has an 'in' parameter) but even if it wasn't, it would still
145+
// be important, because the RenderBatch buffers may be overwritten as soon
146+
// as we yield the sync context. The only alternative would be to clone the
147+
// batch deeply, or serialize it synchronously (e.g., via RenderBatchWriter).
142148
SendBatchAsStreamingUpdate(renderBatch, writer);
143149
return FlushThenComplete(writer, base.UpdateDisplayAsync(renderBatch));
144150
}

0 commit comments

Comments
 (0)