Skip to content

Endpoint renderer waits for quiescence with stopping the rendering #62524

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,7 @@ private Task ReturnErrorResponse(string detailedMessage)

internal async Task SetNotFoundResponseAsync(string baseUri, NotFoundEventArgs args)
{
if (_httpContext.Response.HasStarted ||
// POST waits for quiescence -> rendering the NotFoundPage would be queued for the next batch
// but we want to send the signal to the renderer to stop rendering future batches -> use client rendering
HttpMethods.IsPost(_httpContext.Request.Method))
if (_httpContext.Response.HasStarted)
{
if (string.IsNullOrEmpty(_notFoundUrl))
{
Expand All @@ -104,7 +101,7 @@ internal async Task SetNotFoundResponseAsync(string baseUri, NotFoundEventArgs a
// When the application triggers a NotFound event, we continue rendering the current batch.
// However, after completing this batch, we do not want to process any further UI updates,
// as we are going to return a 404 status and discard the UI updates generated so far.
SignalRendererToFinishRendering();
RequestRendererToFinishRendering();
}

private string GetNotFoundUrl(string baseUri, NotFoundEventArgs args)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,8 @@ internal async ValueTask<PrerenderedComponentHtmlContent> RenderEndpointComponen
var component = BeginRenderingComponent(rootComponentType, parameters);
var result = new PrerenderedComponentHtmlContent(Dispatcher, component);

FinishRendereingOnQuiescenceIfRequested(result);

await WaitForResultReady(waitForQuiescence, result);

return result;
Expand All @@ -159,6 +161,19 @@ internal async ValueTask<PrerenderedComponentHtmlContent> RenderEndpointComponen
}
}

private void FinishRendereingOnQuiescenceIfRequested(PrerenderedComponentHtmlContent htmlContent)
{
if (htmlContent.QuiescenceTask.IsCompleted)
{
SignalRendererToFinishRendering();
return;
}

htmlContent.QuiescenceTask.ContinueWith(
_ => SignalRendererToFinishRendering(),
TaskScheduler.Default);
}

private async Task WaitForResultReady(bool waitForQuiescence, PrerenderedComponentHtmlContent result)
{
if (waitForQuiescence)
Expand Down
13 changes: 11 additions & 2 deletions src/Components/Endpoints/src/Rendering/EndpointHtmlRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ internal partial class EndpointHtmlRenderer : StaticHtmlRenderer, IComponentPrer
private HttpContext _httpContext = default!; // Always set at the start of an inbound call
private ResourceAssetCollection? _resourceCollection;
private bool _rendererIsStopped;
private bool _rendererStopRequested;
private readonly ILogger _logger;

// The underlying Renderer always tracks the pending tasks representing *full* quiescence, i.e.,
Expand Down Expand Up @@ -185,11 +186,19 @@ protected override void AddPendingTask(ComponentState? componentState, Task task
base.AddPendingTask(componentState, task);
}

internal void RequestRendererToFinishRendering()
{
// requests a deferred stop of the renderer, which will have an effect after the current batch is completed
_rendererStopRequested = true;
}

// For testing purposes only
internal void SignalRendererToFinishRendering()
{
// sets a deferred stop on the renderer, which will have an effect after the current batch is completed
_rendererIsStopped = true;
if (_rendererStopRequested)
{
_rendererIsStopped = true;
}
}

protected override void ProcessPendingRender()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,22 @@ public void NotFoundSetOnInitialization_ResponseNotStarted_SSR(bool hasReExecuti
string testUrl = $"{ServerPathBase}{reexecution}/set-not-found-ssr?useCustomNotFoundPage={hasCustomNotFoundPageSet}";
Navigate(testUrl);

if (hasCustomNotFoundPageSet)
{
AssertNotFoundPageRendered();
}
else
{
AssertNotFoundFragmentRendered();
}
AssertNotFoundRendered_ResponseNotStarted(hasCustomNotFoundPageSet);
AssertUrlNotChanged(testUrl);
}

[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void NotFoundSetOnInitialization_AfterAsyncOperation_ResponseNotStarted_SSR(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet)
{
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
string testUrl = $"{ServerPathBase}{reexecution}/set-not-found-ssr?doAsync=true&useCustomNotFoundPage={hasCustomNotFoundPageSet}";
Navigate(testUrl);

AssertNotFoundRendered_ResponseNotStarted(hasCustomNotFoundPageSet);
AssertUrlNotChanged(testUrl);
}

Expand All @@ -162,7 +170,8 @@ public void NotFoundSetOnInitialization_ResponseStarted_SSR(bool hasReExecutionM
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
string testUrl = $"{ServerPathBase}{reexecution}/set-not-found-ssr-streaming?useCustomNotFoundPage={hasCustomNotFoundPageSet}";
Navigate(testUrl);
AssertNotFoundRendered_ResponseStarted_Or_POST(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);

AssertNotFoundRendered_ResponseStarted(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
AssertUrlNotChanged(testUrl);
}

Expand All @@ -176,11 +185,12 @@ public void NotFoundSetOnInitialization_ResponseStarted_EnhancedNavigationDisabl
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
string testUrl = $"{ServerPathBase}{reexecution}/set-not-found-ssr-streaming?useCustomNotFoundPage={hasCustomNotFoundPageSet}";
Navigate(testUrl);
AssertNotFoundRendered_ResponseStarted_Or_POST(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);

AssertNotFoundRendered_ResponseStarted(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
AssertUrlChanged(testUrl);
}

private void AssertNotFoundRendered_ResponseStarted_Or_POST(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet, string testUrl)
private void AssertNotFoundRendered_ResponseStarted(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet, string testUrl)
{
if (hasCustomNotFoundPageSet)
{
Expand All @@ -197,6 +207,18 @@ private void AssertNotFoundRendered_ResponseStarted_Or_POST(bool hasReExecutionM
}
}

private void AssertNotFoundRendered_ResponseNotStarted(bool hasCustomNotFoundPageSet)
{
if (hasCustomNotFoundPageSet)
{
AssertNotFoundPageRendered();
}
else
{
AssertNotFoundFragmentRendered();
}
}

[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
Expand All @@ -209,7 +231,23 @@ public void NotFoundSetOnFormSubmit_ResponseNotStarted_SSR(bool hasReExecutionMi
Navigate(testUrl);
Browser.FindElement(By.Id("not-found-form")).FindElement(By.TagName("button")).Click();

AssertNotFoundRendered_ResponseStarted_Or_POST(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
AssertNotFoundRendered_ResponseStarted(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
AssertUrlNotChanged(testUrl);
}

[Theory]
[InlineData(true, true)]
[InlineData(true, false)]
[InlineData(false, true)]
[InlineData(false, false)]
public void NotFoundSetOnFormSubmit_AfterAsyncOperation_ResponseNotStarted_SSR(bool hasReExecutionMiddleware, bool hasCustomNotFoundPageSet)
{
string reexecution = hasReExecutionMiddleware ? "/reexecution" : "";
string testUrl = $"{ServerPathBase}{reexecution}/post-not-found-ssr?doAsync=true&useCustomNotFoundPage={hasCustomNotFoundPageSet}";
Navigate(testUrl);
Browser.FindElement(By.Id("not-found-form")).FindElement(By.TagName("button")).Click();

AssertNotFoundRendered_ResponseNotStarted(hasCustomNotFoundPageSet);
AssertUrlNotChanged(testUrl);
}

Expand All @@ -225,7 +263,7 @@ public void NotFoundSetOnFormSubmit_ResponseStarted_SSR(bool hasReExecutionMiddl
Navigate(testUrl);
Browser.FindElement(By.Id("not-found-form")).FindElement(By.TagName("button")).Click();

AssertNotFoundRendered_ResponseStarted_Or_POST(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
AssertNotFoundRendered_ResponseStarted(hasReExecutionMiddleware, hasCustomNotFoundPageSet, testUrl);
AssertUrlNotChanged(testUrl);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Forms

@inject NavigationManager NavigationManager

Expand All @@ -16,17 +16,17 @@

@code{
[Parameter]
public bool StartStreaming { get; set; } = false;
public bool DoAsyncOperationBeforeSettingNotFound { get; set; } = false;

[Parameter]
public bool WaitForInteractivity { get; set; } = false;

private async Task HandleSubmit()
{
if (StartStreaming)
if (DoAsyncOperationBeforeSettingNotFound)
{
await Task.Yield();
}
NavigationManager.NotFound();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@inject NavigationManager NavigationManager
@inject NavigationManager NavigationManager

@if (!WaitForInteractivity || RendererInfo.IsInteractive)
{
Expand All @@ -10,17 +10,17 @@

@code{
[Parameter]
public bool StartStreaming { get; set; } = false;
public bool DoAsyncOperationBeforeSettingNotFound { get; set; } = false;

[Parameter]
public bool WaitForInteractivity { get; set; } = false;

protected async override Task OnInitializedAsync()
{
if (StartStreaming)
if (DoAsyncOperationBeforeSettingNotFound)
{
await Task.Yield();
}
NavigationManager.NotFound();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@
interactive later if interactivity was enabled in the app
*@

<TestContentPackage.NotFound.ComponentThatPostsNotFound />
<TestContentPackage.NotFound.ComponentThatPostsNotFound DoAsyncOperationBeforeSettingNotFound="@DoAsync" />

@code{
[SupplyParameterFromQuery(Name = "doAsync")]
public bool DoAsync { get; set; } = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
interactive later if interactivity was enabled in the app
*@

<TestContentPackage.NotFound.ComponentThatPostsNotFound StartStreaming="true"/>
<TestContentPackage.NotFound.ComponentThatPostsNotFound DoAsyncOperationBeforeSettingNotFound="true"/>
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,9 @@
interactive later if interactivity was enabled in the app
*@

<TestContentPackage.NotFound.ComponentThatSetsNotFound />
<TestContentPackage.NotFound.ComponentThatSetsNotFound DoAsyncOperationBeforeSettingNotFound="@DoAsync" />

@code{
[SupplyParameterFromQuery(Name = "doAsync")]
public bool DoAsync { get; set; } = false;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
interactive later if interactivity was enabled in the app
*@

<TestContentPackage.NotFound.ComponentThatSetsNotFound StartStreaming="true"/>
<TestContentPackage.NotFound.ComponentThatSetsNotFound DoAsyncOperationBeforeSettingNotFound="true"/>
Loading