Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
Currently, it is not possible to start fetching 2 or more byte arrays via JSInterop at once.
This is a problem as larger sites might wish to retrieve multiple byte arrays independently simultaneously.
In my use case, I do this when displaying plots of sound amplitudes in the demos of Blazor.WebAudio. Currently, it either breaks randomly if two plots try to refresh at the same time or I have to do more scheduling work to ensure that no two arrays are being fetched at the same time.
I have a minimal sample of this bug in this repo: https://github.com/KristofferStrube/BlazorFailsToDeserializeTwoByteArraysAtOnce
Which can be demoed live here: https://kristofferstrube.github.io/BlazorFailsToDeserializeTwoByteArraysAtOnce/
If you try out the demo you see that we get the following exception:
JSException: An exception occurred executing JS interop: JSON serialization is attempting to deserialize an unexpected byte array.. See InnerException for more details.
This is thrown from here: ByteArrayJsonConverter.cs#L33
if (JSRuntime.ByteArraysToBeRevived.Count == 0)
{
throw new JsonException("JSON serialization is attempting to deserialize an unexpected byte array.");
}
ByteArraysToBeRevived
becomes empty when another JSInterop invocation has started before the current one finishes deserializing the response as can be seen here: JSRuntime.cs#L200
/// <summary>
/// Accepts the byte array data being transferred from JS to DotNet.
/// </summary>
/// <param name="id">Identifier for the byte array being transfered.</param>
/// <param name="data">Byte array to be transfered from JS.</param>
protected internal virtual void ReceiveByteArray(int id, byte[] data)
{
if (id == 0)
{
// Starting a new transfer, clear out previously stored byte arrays
// in case they haven't been cleared already.
ByteArraysToBeRevived.Clear();
}
if (id != ByteArraysToBeRevived.Count)
{
throw new ArgumentOutOfRangeException($"Element id '{id}' cannot be added to the byte arrays to be revived with length '{ByteArraysToBeRevived.Count}'.", innerException: null);
}
ByteArraysToBeRevived.Append(data);
}
So the core of the problem is that all calls that return a byte array use the same ArrayBuilder<byte[]> ByteArraysToBeRevived
instance.
Expected Behavior
The expected behavior is that we can do multiple JSInterop calls that return byte arrays at once.
This can be fixed by changing ByteArraysToBeRevived
to be a concurrent dictionary from a long id to a byte array and then not resetting the id that we use to identify each byte array argument in a single invocation which is called nextByteArrayIndex
.
This would mean that we no longer use ArrayBuilder<byte[]>
, which was smart as Blazor would then automatically throw away any references to byte arrays that didn't end up being deserialized. To get around this we would need to ensure that we also "release" or "return" those arrays that we didn't end up deserializing.
I would love to supply and collaborate on a PR for fixing this.
Unrelated:
There currently is an unrelated bug that we don't clear ByteArraysToBeRevived
when a JSInterop task is canceled. The problem is negated by the JSInterop code pessimistically clearing it anyway when making a new call that returns a byte array (or more). Which is probably why that has not been reported previously.
Steps To Reproduce
I have a minimal sample of this bug in this repo: https://github.com/KristofferStrube/BlazorFailsToDeserializeTwoByteArraysAtOnce
Which can be demoed live here: https://kristofferstrube.github.io/BlazorFailsToDeserializeTwoByteArraysAtOnce/
Exceptions (if any)
JSException: An exception occurred executing JS interop: JSON serialization is attempting to deserialize an unexpected byte array.. See InnerException for more details.
.NET Version
9.0.101
Anything else?
ASP.NET Core version 9.0.0
IDE: VSCode or Visual Studio
dotnet -info:
.NET SDK:
Version: 9.0.101
Commit: eedb237549
Workload version: 9.0.100-manifests.3068a692
MSBuild version: 17.12.12+1cce77968
Runtime Environment:
OS Name: Windows
OS Version: 10.0.19041
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\9.0.101\