Blazorise
Blazorise copied to clipboard
Bar BreakpointAdapter throws DisposedException after quick redirect
When using the Bar component from Blazorise 1.0.6 I see the following exception passing by:
crit: Microsoft.AspNetCore.Components.WebAssembly.Rendering.WebAssemblyRenderer[100]
Unhandled exception rendering component: Cannot access a disposed object.
Object name: 'DotNetObjectReference`1'.
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'DotNetObjectReference`1'.
at Microsoft.JSInterop.DotNetObjectReference`1[[Blazorise.BreakpointActivatorAdapter, Blazorise, Version=1.0.6.0, Culture=neutral, PublicKeyToken=null]].ThrowIfDisposed()
at Microsoft.JSInterop.JSRuntime.TrackObjectReference[BreakpointActivatorAdapter](DotNetObjectReference`1 dotNetObjectReference)
at Microsoft.JSInterop.Infrastructure.DotNetObjectReferenceJsonConverter`1[[Blazorise.BreakpointActivatorAdapter, Blazorise, Version=1.0.6.0, Culture=neutral, PublicKeyToken=null]].Write(Utf8JsonWriter writer, DotNetObjectReference`1 value, JsonSerializerOptions options)
at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.JSInterop.DotNetObjectReference`1[[Blazorise.BreakpointActivatorAdapter, Blazorise, Version=1.0.6.0, Culture=neutral, PublicKeyToken=null]], Microsoft.JSInterop, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].TryWrite(Utf8JsonWriter writer, DotNetObjectReference`1& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1[[Microsoft.JSInterop.DotNetObjectReference`1[[Blazorise.BreakpointActivatorAdapter, Blazorise, Version=1.0.6.0, Culture=neutral, PublicKeyToken=null]], Microsoft.JSInterop, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].TryWriteAsObject(Utf8JsonWriter writer, Object value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1[[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(Utf8JsonWriter writer, Object& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.Converters.ArrayConverter`2[[System.Object[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnWriteResume(Utf8JsonWriter writer, Object[] array, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonCollectionConverter`2[[System.Object[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Object, System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnTryWrite(Utf8JsonWriter writer, Object[] value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1[[System.Object[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].TryWrite(Utf8JsonWriter writer, Object[]& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.Serialization.JsonConverter`1[[System.Object[], System.Private.CoreLib, Version=6.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].WriteCore(Utf8JsonWriter writer, Object[]& value, JsonSerializerOptions options, WriteStack& state)
at System.Text.Json.JsonSerializer.WriteUsingSerializer[Object[]](Utf8JsonWriter writer, Object[]& value, JsonTypeInfo jsonTypeInfo)
at System.Text.Json.JsonSerializer.WriteStringUsingSerializer[Object[]](Object[]& value, JsonTypeInfo jsonTypeInfo)
at System.Text.Json.JsonSerializer.Serialize[Object[]](Object[] value, JsonSerializerOptions options)
at Microsoft.JSInterop.JSRuntime.InvokeAsync[IJSVoidResult](Int64 targetInstanceId, String identifier, CancellationToken cancellationToken, Object[] args)
at Microsoft.JSInterop.JSRuntime.<InvokeAsync>d__16`1[[Microsoft.JSInterop.Infrastructure.IJSVoidResult, Microsoft.JSInterop, Version=6.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60]].MoveNext()
at Microsoft.JSInterop.JSObjectReferenceExtensions.InvokeVoidAsync(IJSObjectReference jsObjectReference, String identifier, Object[] args)
at Blazorise.Modules.JSBreakpointModule.RegisterBreakpoint(DotNetObjectReference`1 dotNetObjectRef, String elementId)
at Blazorise.Bar.OnFirstAfterRenderAsync()
at Blazorise.BaseComponent.OnAfterRenderAsync(Boolean firstRender)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
This is probably happening because my blazor app detects there is no user authenticated yet and redirect to the authentication service so not allowing all services to correctly setup/render before being disposed again.
Some additional check is probably required in the RegisterBreakpoint on if the JsModule is disposed again
We had similar error reports when Bar was used with authentication, so I would guess the same thing is happening again. But without being able to reproduce it on our side it would be hard to fix.
If I look at the code then I can see there is a possible race condition:
1. var moduleInstance = await Module;
2. await moduleInstance.InvokeVoidAsync( "registerBreakpointComponent", dotNetObjectRef, elementId );
Between step 1 and step 2 the Module can be disposed so before invoking on the module it should be checked if its disposed.
var moduleInstance = await Module;
if (!AsyncDisposed)
await moduleInstance.InvokeVoidAsync( "registerBreakpointComponent", dotNetObjectRef, elementId );
I see this construction everywhere so possibly everywhere this dispose race-condition could occur
Maybe a helper method for save invocation:
protected async ValueTask InvokeVoidSafeAsync( string identifier, params object[] args )
{
if ( IsUnsafe ) return;
var module = await moduleTask;
if (AsyncDisposed) return;
await module.InvokeVoidAsync( identifier, args );
}
protected async ValueTask<T> InvokeSafeAsync<T>( string identifier, params object[] args )
{
if ( IsUnsafe )
return default;
var module = await moduleTask;
if ( AsyncDisposed )
return default;
return await module.InvokeAsync<T>( identifier, args );
}
To add onto this, I think I have seen Microsoft's implementations doing a try catch on JSDisconnectedException
.
I think the helper method might be the cleanest way to go. Maybe it can even include JSDisconnectedException.
what would you like that happens on a JSDisconnectedException? Also return the default value?
protected async ValueTask InvokeSafeAsync<T>( string identifier, params object[] args )
{
if ( IsUnsafe ) return default;
try
{
var module = await moduleTask;
if (AsyncDisposed) return default;
await module.InvokeVoidAsync( identifier, args );
}
catch (Exception e)
when (e is JSDisconnectedException
or ObjectDisposedException
or TaskCanceledException)
{
return default;
}
}
I just tested this with 1.0.7 and the exception is gone. Thanks @stsrki
I just tested this with 1.0.7 and the exception is gone. Thanks @stsrki
Great, Thanks for testing.