Skip to content
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

5.1.0 - ErrorBoundary Not Working #3907

Closed
sbwalker opened this issue Feb 27, 2024 · 7 comments
Closed

5.1.0 - ErrorBoundary Not Working #3907

sbwalker opened this issue Feb 27, 2024 · 7 comments

Comments

@sbwalker
Copy link
Member

sbwalker commented Feb 27, 2024

The ErrorBoundary for modules is no longer working for unhandled exceptions. The background information related to how Oqtane uses ErrorBoundary is described in this blog:

https://www.oqtane.org/blog/!/34/errorboundary-and-logging-in-blazor

To reproduce the issue simply throw an exception from a component. I added logic to Oqtane.Client\Modules\HtmlText\Edit.razor so that I could go into edit mode and click on the Edit button for the Html/Text module to trigger the error:

protected override async Task OnInitializedAsync()
{
        throw new Exception("Test");
        ...

I have tested this on both Static and Interactive rendering and it has the same problem. The OnErrorAsync() method catches the error and logs it properly, however the framework then crashes with the following cryptic error:

System.ArgumentException: The renderer does not have a component with ID 50.
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetRequiredComponentState(Int32 componentId)
   at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.GetComponentDepth(Int32 componentId)
   at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.SendBatchAsStreamingUpdate(RenderBatch& renderBatch, TextWriter writer)
   at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.UpdateDisplayAsync(RenderBatch& renderBatch)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.HandleException(Exception exception)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
   at Microsoft.AspNetCore.Components.RenderHandle.Render(RenderFragment renderFragment)
   at Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()
   at Oqtane.UI.Routes.ChangeState(PageState pageState) in C:\Source\Projects\oqtane.framework\Oqtane.Client\UI\Routes.razor:line 101
   at Oqtane.UI.SiteRouter.Refresh() in C:\Source\Projects\oqtane.framework\Oqtane.Client\UI\SiteRouter.razor:line 303
   at Oqtane.UI.SiteRouter.OnParametersSetAsync() in C:\Source\Projects\oqtane.framework\Oqtane.Client\UI\SiteRouter.razor:line 70
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
   at Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.HandleException(Exception exception)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.HandleExceptionViaErrorBoundary(Exception error, ComponentState errorSourceOrNull)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.<WaitForQuiescence>g__ProcessAsynchronousWork|54_0()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.WaitForQuiescence()
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(Int32 componentId, ParameterView initialParameters)
   at Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.SendStreamingUpdatesAsync(HttpContext httpContext, Task untilTaskCompleted, TextWriter writer)

In the migration to Blazor Web, the ErrorBoundary was moved from ModuleInstance to RenderModeBoundary so that it is on the interactive side of the render mode boundary. Additional changes have also been made to the ModuleMessage component recently which the ErrorBoundary relies upon. I have not been able to verify if any of these changes have caused the problem - or if this issue existed in the 5.0.2 release as well, and is related to .NET 8.

The ErrorBoundary component from Blazor is located here:

https://github.com/dotnet/aspnetcore/blob/6a8083a9c4a6f28c21c8564e08d56bbe48e6b16b/src/Components/Web/src/Web/ErrorBoundary.cs#L20

It is important that Oqtane is able to capture unhandled exception for modules so this will need to be resolved for 5.1.

@thabaum
Copy link
Contributor

thabaum commented Feb 27, 2024

I wonder if it has something to do with stream rendering? Maybe try removing it and see if in interactive mode the issue still exists?

I will be able to work some today so I will test later all current issues and assist as I can troubleshooting in a couple hours.

@sbwalker
Copy link
Member Author

@thabaum StreamRendering has no effect

@thabaum
Copy link
Contributor

thabaum commented Feb 27, 2024

@sbwalker I was reviewing ErrorBoundaryBase.cs, would this comment potentially relate https://github.com/dotnet/aspnetcore/blob/c85baf8db0c72ae8e68643029d514b2e737c9fae/src/Components/Components/src/ErrorBoundaryBase.cs#L106C1-L109C94
or the one above that is describing a fatal error issue.

@sbwalker
Copy link
Member Author

@thabaum the ErrorBoundaryBase code has not changed in 3 years, so I am wondering why the logic in Oqtane is suddenly failing. If I remove all logic from the OnErrorAsync method it still throws an error.

    protected override async Task OnErrorAsync(Exception exception)
    {
        await base.OnErrorAsync(exception);
    }

@thabaum
Copy link
Contributor

thabaum commented Feb 27, 2024

@sbwalker
Copy link
Member Author

sbwalker commented Feb 27, 2024

@thabaum after futher investigation I believe the specific error scenario I am testing has never worked in Oqtane... and is in fact not a valid scenario.

Throwing an unhandled exception in OnInitializedAsync:

protected override async Task OnInitializedAsync()
{
        throw new Exception("Test");

basically creates an infinite loop scenario, as the await keyword within the OnErrorAsync method will trigger a re-render automatically when it completes (this is standard Blazor behavior) - which will call OnInitializedAsync for the component.... which throws the exception again... etc...

If I throw an exception from a button click handler:

private async Task SaveContent()
{        
        throw new Exception("Test");

the logic works fine and displays the error message modal dialog in the UI.

Note that ErrorBoundary is only intended for unhandled exceptions. It is always best practice in Blazor to include try... catch blocks within your component methods so that exceptions are explicitly handled (this is the same guidance I provided in the blog link above). Oqtane follows this approach which is why the error "System.ArgumentException: The renderer does not have a component with ID" has never surfaced previously.

Therefore I am closing this issue as it has an explanation and known patterns to avoid it.

@thabaum
Copy link
Contributor

thabaum commented Feb 27, 2024

@sbwalker I was wondering the same and was about to test 5.02 as I just got back... thanks for the update and added info.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants