Blazor SSR Task throws NavigationException from finalizer thread
Is there an existing issue for this?
- [x] I have searched the existing issues
Describe the bug
When Blazor is used with SSR, and the OnInitializedAsync method throws an error such as the NavigationException, the task can end up on the finalizer thread.
It appears to require the OnInitializedAsync method to have made an async call first. However, even this is not reliable reproduction.
When I call NavigateTo() in OnInitializedAsync (server side) the browser is successfully redirected using navigationManager.NavigateTo(), but the NavigationException still hits the finalizer thread. In production it can take up to 22 minutes before the I saw it hit the "unobserved exception was rethrown by the finalizer thread" error.
Expected Behavior
NavigationException should be caught by the blazor code to trigger navigation. It should not be rethrown by the finalizer thread.
Steps To Reproduce
- Create an OnInitializedAsync method with 2s delay
- Then call navigationManager.NavigateTo("/");
- Call GC.Collect and WaitForPendingFinalizers to ensure prompt reproduction
Load the following example in the web browser. It should redirect to itself. The second time it is called, the errors should be in the console.
Index.razor
@page "/"
@using BlazorApp1
<h1>Hello, world!</h1>
@code {
[Inject]
protected NavigationManager navigationManager { get; set; } = default!;
protected override async Task OnInitializedAsync() {
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
await Task.Delay(2000);
navigationManager.NavigateTo("/");
}
}
App.razor
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using BlazorApp1
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData">
<RouteView RouteData="@routeData"/>
</Found>
</Router>
Program.cs:
using BlazorApp1;
using System.Runtime.InteropServices;
Console.WriteLine("Framework: " + RuntimeInformation.FrameworkDescription);
Console.WriteLine("Runtime Identifier: " + RuntimeInformation.RuntimeIdentifier);
AppDomain.CurrentDomain.UnhandledException += (sender, e) => {
Console.WriteLine("Unhandled caught");
};
TaskScheduler.UnobservedTaskException += (sender, e) => {
Console.WriteLine("Unhandled caught: " + e.Exception.ToString());
e.SetObserved();
};
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorComponents();
var app = builder.Build();
app.UseStaticFiles();
app.UseRouting();
app.UseAntiforgery();
app.MapRazorComponents<App>();
app.Run();
BlazorApp1.csproj:
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
</Project>
Exceptions (if any)
info: Microsoft.AspNetCore.Hosting.Diagnostics[1]
Request starting HTTP/2 GET https://localhost:7275/ - - -
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint '/ (/)'
Unhandled caught: System.AggregateException: A Task's exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread. (Exception of type 'Microsoft.AspNetCore.Components.NavigationException' was thrown.)
---> Microsoft.AspNetCore.Components.NavigationException: Exception of type 'Microsoft.AspNetCore.Components.NavigationException' was thrown.
at Microsoft.AspNetCore.Components.Endpoints.HttpNavigationManager.NavigateToCore(String uri, NavigationOptions options)
at Microsoft.AspNetCore.Components.NavigationManager.NavigateToCore(String uri, Boolean forceLoad)
at Microsoft.AspNetCore.Components.NavigationManager.NavigateTo(String uri, Boolean forceLoad, Boolean replace)
at BlazorApp1.Pages.Index.OnInitializedAsync() in C:\Users\xxx\source\repos\bugcase\BlazorApp1\BlazorApp1\Pages\Index.razor:line 15
at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.<WaitForNonStreamingPendingTasks>g__Execute|43_0()
at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.WaitForResultReady(Boolean waitForQuiescence, PrerenderedComponentHtmlContent result)
at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.RenderEndpointComponent(HttpContext httpContext, Type rootComponentType, ParameterView parameters, Boolean waitForQuiescence)
--- End of inner exception stack trace ---
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint '/ (/)'
info: Microsoft.AspNetCore.Hosting.Diagnostics[2]
Request finished HTTP/2 GET https://localhost:7275/ - 302 0 text/html;+charset=utf-8 2476.9080ms
.NET Version
9.0.300
Anything else?
dotnet --version 9.0.300
Microsoft Visual Studio Community 2022 (64-bit) - Version 17.14.3
dotnet --info .NET SDK: Version: 9.0.300 Commit: 15606fe0a8 Workload version: 9.0.300-manifests.af4147de MSBuild version: 17.14.5+edd3bbf37
Runtime Environment: OS Name: Windows OS Version: 10.0.26100 OS Platform: Windows RID: win-x64 Base Path: C:\Program Files\dotnet\sdk\9.0.300\
.NET workloads installed: [aspire] Installation Source: VS 17.14.36127.28 Manifest Version: 8.2.2/8.0.100 Manifest Path: C:\Program Files\dotnet\sdk-manifests\8.0.100\microsoft.net.sdk.aspire\8.2.2\WorkloadManifest.json Install Type: Msi
Configured to use loose manifests when installing new manifests.
Host: Version: 9.0.5 Architecture: x64 Commit: e36e4d1a8f
.NET SDKs installed: 9.0.300 [C:\Program Files\dotnet\sdk]
.NET runtimes installed: Microsoft.AspNetCore.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 8.0.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.AspNetCore.App 9.0.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App] Microsoft.NETCore.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 8.0.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 9.0.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.WindowsDesktop.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 8.0.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 9.0.5 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Other architectures found: x86 [C:\Program Files (x86)\dotnet] registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]
Environment variables: Not set
global.json file: Not found
Learn more: https://aka.ms/dotnet/info
Download .NET: https://aka.ms/dotnet/download
@EffectivelyEfficient thanks for contacting us.
We'll take a look and see what's going on.
For 10.0 we are changing the way this works on SSR to avoid throwing an exception at all. I'm puzzled about how this can even happen.
Do you happen to have the project you created to describe the issue? Could you please push it to a public github repository? That help us ensure that we don't miss or mess anything up when we try things on our end.
Here is a minimal repro project that illustrates the problem. https://github.com/EffectivelyEfficient/BugBlazorNavigationExceptionFromFinalizer On my machine it reproduces the problem reliably and within a few seconds.
Also note this is for a non-streaming scenario. This means there is no @attribute [StreamRendering] on Index.razor.
Putting @attribute [StreamRendering] on Index.razor stops the error, but it also prevents navigationManager.NavigateTo() from redirecting.
@EffectivelyEfficient thanks for the additional details.
We'll take a look and update the issue accordingly.
I'm puzzled about how this can even happen.
@javiercn, logging this internal exception is an expected behavior in net9 in this setup and this reproduction just shows that it's being thrown. I think I don't understand what you intended to do here. The only option that comes to my mind is to backport the fix: https://github.com/dotnet/aspnetcore/pull/61306 + https://github.com/dotnet/aspnetcore/pull/62358.