aspnetcore: Blazor Index Out of Range Exception
Describe the bug
Exception thrown: ‘System.IndexOutOfRangeException’ in System.Private.CoreLib.dll System.IndexOutOfRangeException: Index was outside the bounds of the array. at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
I have a periodic task that runs at 1Hz and components that get their data and update their state call StateHasChanged() at that frequency.
To Reproduce
- Using this version of ASP.NET Core: Blazor 0.7.0
- Run this code:
_updateTimer = new Timer((state) =>
{
try
{
Updated();
}
catch(Exception ext)
{
System.Diagnostics.Trace.WriteLine(ext.ToString());
}
}, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1));
- With these arguments: Blazor Components that register for this event call StateHasChanged() when appropriate.
- See error:
Exception thrown: 'System.IndexOutOfRangeException' in System.Private.CoreLib.dll
System.IndexOutOfRangeException: Index was outside the bounds of the array.
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at Microsoft.AspNetCore.Blazor.Rendering.Renderer.AssignEventHandlerId(RenderTreeFrame& frame)
at Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForAttributeFrame(DiffContext& diffContext, Int32 oldFrameIndex, Int32 newFrameIndex)
at Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeDiffBuilder.AppendAttributeDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
at Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForFramesWithSameSequence(DiffContext& diffContext, Int32 oldFrameIndex, Int32 newFrameIndex)
at Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(DiffContext& diffContext, Int32 oldStartIndex, Int32 oldEndIndexExcl, Int32 newStartIndex, Int32 newEndIndexExcl)
at Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, Int32 componentId, ArrayRange`1 oldTree, ArrayRange`1 newTree)
at Microsoft.AspNetCore.Blazor.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment)
at Microsoft.AspNetCore.Blazor.Rendering.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)
at Microsoft.AspNetCore.Blazor.Rendering.Renderer.ProcessRenderQueue()
at Microsoft.AspNetCore.Blazor.Rendering.Renderer.AddToRenderQueue(Int32 componentId, RenderFragment renderFragment)
at Microsoft.AspNetCore.Blazor.Server.Circuits.CircuitSynchronizationContext.ExecuteSynchronously(TaskCompletionSource`1 completion, SendOrPostCallback d, Object state)
at Microsoft.AspNetCore.Blazor.Server.Circuits.CircuitSynchronizationContext.Post(SendOrPostCallback d, Object state)
at NG.Blazor.Client.Components.Settings.<OnAfterRender>b__10_0() in C:\compile\BlazorEcdis\NG.Blazor.Client\Components\Settings.cshtml:line 32
at NG.Blazor.Client.DataService.<.ctor>b__7_0(Object state) in C:\compile\BlazorEcdis\NG.Blazor.Client\DataService.cs:line 31
Expected behavior
Dictionary can be added to.
Screenshots
N/A
Additional context
Likely this could be addressed by changing the Dictionary that houses the _eventBindings (and _componentStateById?) to a Concurrent Dictionary here: https://github.com/aspnet/AspNetCore/blob/436b5461ad0337aac90089aa7ccb61dd875808e4/src/Components/src/Microsoft.AspNetCore.Components/Rendering/Renderer.cs
About this issue
- Original URL
- State: closed
- Created 5 years ago
- Reactions: 1
- Comments: 16 (3 by maintainers)
Commits related to this issue
- Eliminate ContinueWith from Renderer.cs. Fixes #6385 — committed to dotnet/aspnetcore by SteveSandersonMS 5 years ago
I’ve done some investigation, and now would like to discuss and re-triage. cc @mkArtakMSFT
Although I’ve been unable to reproduce the
IndexOutOfRangeExceptionpersonally, I think I’ve found two bugs that are behind this problem.1. Use of
ContinueWithinRenderer.csWe still have a use of
ContinueWithhere: https://github.com/aspnet/AspNetCore/blob/master/src/Components/Components/src/Rendering/Renderer.cs#L625This escapes from the renderer’s sync context, leading to general thread-safety issues. In particular, the code that runs here tries to mutate
_eventBindings. This is basically what @springy76 was reporting here. Note that the fix is not to use locking, but rather to ensure we stay on the correct sync context so no locking is needed.2. MessagePack’s
ReadStringSlowlooks brokenFixing (1) above isn’t sufficient. The connection can still become broken, because when there’s enough contention, incoming messages get corrupted. What I was seeing was it trying to call a SignalR Hub called
OnOnOnOnOnOnOnOnO, when it should have been callingOnRenderCompleted.I think I’ve traced this down to a bug around here: https://github.com/aspnet/MessagePack-CSharp/blob/master/src/MessagePack/MessagePackReader.cs#L791
Notice that inside this
whileloop, it never callsreader.Advanceat all. So if the current span is shorter than the length of the entire string, it will just keep copying the contents of the current span over and over until it fills up the correct output length. In my case, the initial span was length 2, so it just keeps duplicatingOnuntil it gets to the desired number of characters.If I modify the code to put
this.reader.Advance(bytesRead);right at the end of thewhileloop, it starts working OK.Since this is so completely broken and doesn’t seem to have been reported yet, it looks like
ReadStringSlowis only used incredibly rarely, and you really have to stress the system to get into a scenario where more than one span is involved.Blazor team: let’s discuss how best to get this fix into the MessagePack code.
For those tracking this ticket, it looks like some work has been done for the next release that should resolve this issue. Appears that the calls to the Renderer are going to be passed through an Invoker to ensure there won’t be cross threaded access to the Dictionaries.