LostBeard / SpawnDev.BlazorJS

Full Blazor WebAssembly and Javascript Interop with multithreading via WebWorkers
https://blazorjs.spawndev.com
MIT License
78 stars 6 forks source link

StackOverflowException at uint.TryConvertFromTruncating<uint16> (uint16,uint&) #34

Closed waleed-alharthi closed 1 month ago

waleed-alharthi commented 1 month ago

Describe the bug When trying to run a method in a service from a worker, the following debug messages and error comes up, the method does not run.

Got WebWorker instance Id 840b320e-8d30-4bb1-8998-3288312843a7
blazor.webassembly.js:1 Got ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>() instance Id 840b320e-8d30-4bb1-8998-3288312843a7
blazor.webassembly.js:1  [ERROR] FATAL UNHANDLED EXCEPTION: System.StackOverflowException: The requested operation caused a stack overflow.
Dt @ blazor.webassembly.js:1
blazor.webassembly.js:1  at uint.TryConvertFromTruncating<uint16> (uint16,uint&) [0x0016b] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at uint.CreateTruncating<uint16> (uint16) [0x0002c] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Text.Ascii.ChangeCase<uint16, uint16, System.Text.Ascii/ToLowerConversion> (uint16*,uint16*,uintptr) [0x00318] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Text.Ascii.ChangeCase<uint16, uint16, System.Text.Ascii/ToLowerConversion> (System.ReadOnlySpan`1<uint16>,System.Span`1<uint16>,int&) [0x00062] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Text.Ascii.ToLower (System.ReadOnlySpan`1<char>,System.Span`1<char>,int&) [0x0000d] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Globalization.TextInfo.ChangeCaseCommon<System.Globalization.TextInfo/ToLowerConversion> (System.ReadOnlySpan`1<char>,System.Span`1<char>) [0x00035] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Globalization.TextInfo.ChangeCaseCommon<System.Globalization.TextInfo/ToLowerConversion> (string) [0x00119] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at System.Globalization.TextInfo.ToLower (string) [0x0001b] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at string.ToLowerInvariant () [0x00006] in <7938b2e668744d1ab43cf38633aa3fac>:0
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetPropertyJSName (System.Reflection.PropertyInfo) [0x00007] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:54
Dt @ blazor.webassembly.js:1
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo..ctor (System.Type) [0x00338] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:177
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetTypeConversionInfo (System.Type) [0x00012] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:453
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo..ctor (System.Type) [0x00350] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:179
blazor.webassembly.js:1  at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetTypeConversionInfo (System.Type) [0x00012] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:453

The last few lines are repeated many times, maybe a few 100's (see screenshot), then:

blazor.webassembly.js:1 
 (null)
blazor.webassembly.js:1 
 (null)
blazor.webassembly.js:1 
 program exited (with status: 1), but keepRuntimeAlive() is set (counter=1) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown)
SpawnDev.BlazorJS.lib.module.js:305 
 Callback invokeMethod error: 
(2) ['Invoke', {…}]
 null 
ExitStatus {name: 'ExitStatus', message: 'Program terminated with exit(1)', status: 1}
blazor.webassembly.js:1 
 (null)
blazor.webassembly.js:1 
 Unhandled Exception:
blazor.webassembly.js:1 
 StackOverflowException
blazor.webassembly.js:1 
 [ERROR] FATAL UNHANDLED EXCEPTION: System.StackOverflowException: The requested operation caused a stack overflow.
blazor.webassembly.js:1 
 at Microsoft.AspNetCore.Components.Reflection.PropertySetter.CallPropertySetter<MudBlazor.MudAppBar, int> (System.Action`2<MudBlazor.MudAppBar, int>,object,object) [0x0002b] in <b6ecada0543848beb43c97c645b55df3>:0
blazor.webassembly.js:1 
 at Microsoft.AspNetCore.Components.Reflection.PropertySetter.SetValue (object,object) [0x00008] in <b6ecada0543848beb43c97c645b55df3>:0

The error happens in this method (note the debug messages in the error):

public async Task<List<object>> GetToListAsync(Type entityType, CancellationToken cancellationToken = default, bool doSync = true)
{
    if (doSync)
    {
        Console.WriteLine("Getting ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>()");
        var webWorker = await _webWorkerService.GetWebWorker();
        Console.WriteLine($"Got WebWorker instance Id {webWorker?.LocalInfo.InstanceId}");
        var service = webWorker.GetService<ISyncService>();
        Console.WriteLine($"Got ISyncService from _webWorkerService.TaskPool.GetService<ISyncService>() instance Id {_webWorkerService.InstanceId}");
        await service.SyncRemoteWithLocal(entityType, cancellationToken);
        Console.WriteLine("Finished SyncRemoteWithLocal using _webWorkerService.TaskPool.GetService<ISyncService>()");
    }
    return await _unitOfWork.GetListAsync(entityType,cancellationToken);
}

The first "Console.WriteLine" is never show in the browser's console:

public async Task SyncRemoteWithLocal(Type entityType, CancellationToken cancellationToken = default, int pageNumber = 0)
{
    Console.WriteLine($"SyncService.SyncRemoteWithLocal({entityType.Name}) started");

    _listenerService.TriggerNotifySyncStarted(entityType);

    var localEntitiesSet = _unitOfWork.Get(entityType);
    var localEntities = await localEntitiesSet.Cast<object>().ToListAsync(cancellationToken);

    var remoteCount = await _httpClient.GetCount(entityType, cancellationToken);
    _listenerService.TriggerSyncProgress(entityType, 0, remoteCount / pageSize);

    IEnumerable<object> remoteEntities;
    if (pageNumber == 0)
    {
        //do a sliding window sync
        for (int i = 1; i <= (remoteCount / pageSize)+1; i++)
        {
            _listenerService.TriggerSyncProgress(entityType, i, remoteCount / pageSize);
            remoteEntities = await _httpClient.GetPage(entityType, i, pageSize, cancellationToken);
            await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);

            if (cancellationToken.IsCancellationRequested)
            {
                break;
            }

            _listenerService.TriggerSyncComplete(entityType);
            //if (i == 1)
            //{
            //    _listenerService.TriggerSyncComplete(entityType); //show first page of sync as complete
            //}
        }
    }
    else if (pageNumber > 0)
    {
        remoteEntities = await _httpClient.GetPage(entityType, pageNumber, pageSize, cancellationToken);
        await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);
    }

    _listenerService.TriggerNotifySyncCompleted(entityType);
    _listenerService.TriggerSyncComplete(entityType);
}

To Reproduce

  1. Start with .Net 7 Blazor (WASM + ASP.Net Core Hosted) app.
  2. Upgrade to .Net 8
  3. Add several libraries including Besql
  4. Add a scoped service in Program.cs via its interface.
  5. Get the scoped service via web workers.
  6. Run a method in the scoped service.

Expected behavior The method to run.

Screenshots image

Desktop (please complete the following information):

Additional context Add any other context about the problem here.

LostBeard commented 1 month ago

Just woke up... My first guess is you are trying to call a method on a worker using types that do not serialize or deserialize. Not all types support serialization. I will take a closer look later. 👍

On Fri, May 17, 2024, 1:42 AM Waleed Al Harthi @.***> wrote:

Describe the bug When trying to run a method in a service from a worker, the following debug messages and error comes up, the method does not run.

Got WebWorker instance Id 840b320e-8d30-4bb1-8998-3288312843a7 blazor.webassembly.js:1 Got ISyncService from _webWorkerService.TaskPool.GetService() instance Id 840b320e-8d30-4bb1-8998-3288312843a7 blazor.webassembly.js:1 [ERROR] FATAL UNHANDLED EXCEPTION: System.StackOverflowException: The requested operation caused a stack overflow. Dt @ blazor.webassembly.js:1 blazor.webassembly.js:1 at uint.TryConvertFromTruncating (uint16,uint&) [0x0016b] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at uint.CreateTruncating (uint16) [0x0002c] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at System.Text.Ascii.ChangeCase<uint16, uint16, System.Text.Ascii/ToLowerConversion> (uint16,uint16,uintptr) [0x00318] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at System.Text.Ascii.ChangeCase<uint16, uint16, System.Text.Ascii/ToLowerConversion> (System.ReadOnlySpan1<uint16>,System.Span1,int&) [0x00062] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at System.Text.Ascii.ToLower (System.ReadOnlySpan1<char>,System.Span1,int&) [0x0000d] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at System.Globalization.TextInfo.ChangeCaseCommon<System.Globalization.TextInfo/ToLowerConversion> (System.ReadOnlySpan1<char>,System.Span1) [0x00035] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at System.Globalization.TextInfo.ChangeCaseCommon<System.Globalization.TextInfo/ToLowerConversion> (string) [0x00119] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at System.Globalization.TextInfo.ToLower (string) [0x0001b] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at string.ToLowerInvariant () [0x00006] in <7938b2e668744d1ab43cf38633aa3fac>:0 blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetPropertyJSName (System.Reflection.PropertyInfo) [0x00007] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:54 Dt @ blazor.webassembly.js:1 blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo..ctor (System.Type) [0x00338] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:177 blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetTypeConversionInfo (System.Type) [0x00012] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:453 blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo..ctor (System.Type) [0x00350] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:179 blazor.webassembly.js:1 at SpawnDev.BlazorJS.WebWorkers.TypeConversionInfo.GetTypeConversionInfo (System.Type) [0x00012] in D:\users\tj\Projects\SpawnDev.BlazorJS\SpawnDev.BlazorJS\SpawnDev.BlazorJS.WebWorkers\TypeConversionInfo.cs:453

The last few lines are repeated many times, maybe a few 100's (see screenshot), then:

blazor.webassembly.js:1 (null) blazor.webassembly.js:1 (null) blazor.webassembly.js:1 program exited (with status: 1), but keepRuntimeAlive() is set (counter=1) due to an async operation, so halting execution but not exiting the runtime or preventing further async execution (you can use emscripten_force_exit, if you want to force a true shutdown) SpawnDev.BlazorJS.lib.module.js:305 Callback invokeMethod error: (2) ['Invoke', {…}] null ExitStatus {name: 'ExitStatus', message: 'Program terminated with exit(1)', status: 1} blazor.webassembly.js:1 (null) blazor.webassembly.js:1 Unhandled Exception: blazor.webassembly.js:1 StackOverflowException blazor.webassembly.js:1 [ERROR] FATAL UNHANDLED EXCEPTION: System.StackOverflowException: The requested operation caused a stack overflow. blazor.webassembly.js:1 at Microsoft.AspNetCore.Components.Reflection.PropertySetter.CallPropertySetter<MudBlazor.MudAppBar, int> (System.Action`2<MudBlazor.MudAppBar, int>,object,object) [0x0002b] in :0 blazor.webassembly.js:1 at Microsoft.AspNetCore.Components.Reflection.PropertySetter.SetValue (object,object) [0x00008] in :0

The error happens in this method (note the debug messages in the error):

public async Task<List> GetToListAsync(Type entityType, CancellationToken cancellationToken = default, bool doSync = true) { if (doSync) { Console.WriteLine("Getting ISyncService from _webWorkerService.TaskPool.GetService()"); var webWorker = await _webWorkerService.GetWebWorker(); Console.WriteLine($"Got WebWorker instance Id {webWorker?.LocalInfo.InstanceId}"); var service = webWorker.GetService(); Console.WriteLine($"Got ISyncService from _webWorkerService.TaskPool.GetService() instance Id {_webWorkerService.InstanceId}"); await service.SyncRemoteWithLocal(entityType, cancellationToken); Console.WriteLine("Finished SyncRemoteWithLocal using _webWorkerService.TaskPool.GetService()"); } return await _unitOfWork.GetListAsync(entityType,cancellationToken); }

The first "Console.WriteLine" is never show in the browser's console:

public async Task SyncRemoteWithLocal(Type entityType, CancellationToken cancellationToken = default, int pageNumber = 0) { Console.WriteLine($"SyncService.SyncRemoteWithLocal({entityType.Name}) started");

_listenerService.TriggerNotifySyncStarted(entityType);

var localEntitiesSet = _unitOfWork.Get(entityType);
var localEntities = await localEntitiesSet.Cast<object>().ToListAsync(cancellationToken);

var remoteCount = await _httpClient.GetCount(entityType, cancellationToken);
_listenerService.TriggerSyncProgress(entityType, 0, remoteCount / pageSize);

IEnumerable<object> remoteEntities;
if (pageNumber == 0)
{
    //do a sliding window sync
    for (int i = 1; i <= (remoteCount / pageSize)+1; i++)
    {
        _listenerService.TriggerSyncProgress(entityType, i, remoteCount / pageSize);
        remoteEntities = await _httpClient.GetPage(entityType, i, pageSize, cancellationToken);
        await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);

        if (cancellationToken.IsCancellationRequested)
        {
            break;
        }

        _listenerService.TriggerSyncComplete(entityType);
        //if (i == 1)
        //{
        //    _listenerService.TriggerSyncComplete(entityType); //show first page of sync as complete
        //}
    }
}
else if (pageNumber > 0)
{
    remoteEntities = await _httpClient.GetPage(entityType, pageNumber, pageSize, cancellationToken);
    await MergeLocalWithRemote(entityType, remoteEntities, localEntities, cancellationToken);
}

_listenerService.TriggerNotifySyncCompleted(entityType);
_listenerService.TriggerSyncComplete(entityType);

}

To Reproduce

  1. Start with .Net 7 Blazor (WASM + ASP.Net Core Hosted) app.
  2. Upgrade to .Net 8
  3. Add several libraries including Besql
  4. Add a scoped service in Program.cs via its interface.
  5. Get the scoped service via web workers.
  6. Run a method in the scoped service.

Expected behavior The method to run.

Screenshots image.png (view on web) https://github.com/LostBeard/SpawnDev.BlazorJS/assets/71645462/fa3d98d9-76e5-4c4f-aa0c-c543a7d0be9d

Desktop (please complete the following information):

  • OS: Windows 11
  • Browser: MS Edge
  • Version: 124.0.2478.97 (Official build) (64-bit)

Additional context Add any other context about the problem here.

— Reply to this email directly, view it on GitHub https://github.com/LostBeard/SpawnDev.BlazorJS/issues/34, or unsubscribe https://github.com/notifications/unsubscribe-auth/AFBT677U7SG5THULLACQFCTZCWKE3AVCNFSM6AAAAABH3PE7JSVHI2DSMVQWIX3LMV43ASLTON2WKOZSGMYDCOBXGA4DMNY . You are receiving this because you are subscribed to this thread.Message ID: @.***>

LostBeard commented 1 month ago

This is the method you are calling in your worker from another instance:

public async Task SyncRemoteWithLocal(Type entityType, CancellationToken cancellationToken = default, int pageNumber = 0)

I am fairly certain Type is not serializable at this time and is likely the cause of your issue.

I plan on adding a JsonConverter for Type at some point but I haven't had a need for it yet.

Serialization is required because SpawnDev.BlazorJS.WebWorkers uses postMessage calls to send calls to workers for processing and result reporting.

I only recently added support for serializing CancellationToken, which should work fine for your usage, but I should mention (it is mentioned in the README.md) that CancellationToken will only work as expected when used in async calls that call async methods between cancellation checks on that CancellationToken. That is also due to the need to use postMessage to actually set the CancellationToken's cancelled state. As I said though, this shouldn't be a problem for your usage here.

waleed-alharthi commented 1 month ago

Good catch!

I'll try some dirty workarounds meanwhile, like passing the type as a string then resolving it inside the methods, or placing the web worker in one of the delegated methods so it deals with the resulting "object" instead of "Type".

I'll re-open this issue after doing the due diligence if there is anything new to report.

Edit: just saw the new commit! I'll not change anything until I try the new version! 👌

LostBeard commented 1 month ago

I just uploaded version 2.2.99 which should work for you with that method as is.

waleed-alharthi commented 1 month ago

It seems this resolved the issue with the type, I no longer get that error message.. I now have a new problem resolving a service, which is injected into the ISyncService 😂

I'll create a new issue to keep things clean. 👍