oatsoda
oatsoda

Reputation: 2178

Blazor WebAssembly SignalR HubConnection causing javascript error on reload

I have a SignalR HubConnection within my Blazor WebAssembly application and whilst it works most of the time, if I reload the page (via the browser reload) then I often am getting the following error in the console and the connection is not made:

Uncaught Error: The delegate target that is being invoked is no longer available. Please check if it has been prematurely GC'd. at Object.invoke_delegate (dotnet.5.0.4.js:1) at WebSocket. (dotnet.5.0.4.js:1)

Here's a rough, simplified view of the code where I create the HubConnection (and dispose it).

@inherits LayoutBase
@attribute [Authorize]

<AuthorizeView>
    <Authorized>
        //...
    </Authorized>
    <NotAuthorized>
        //...
    </NotAuthorized>
</AuthorizeView>
public class LayoutBase : LayoutComponentBase, IAsyncDisposable
{
    [Inject] public IAccessTokenProvider AccessTokenProvider { get; set; }
    
    private readonly HubConnection _hubConnection;
    
    protected override async Task OnInitializedAsync()
    {
        _hubConnection = new HubConnectionBuilder()
                    .AddNewtonsoftJsonProtocol(c =>
                    {
                        //...
                    })
                    .WithUrl(notificationHubUrl, option => option.AccessTokenProvider = GetAccessToken)
                    .WithAutomaticReconnect()
                    .Build();                   
                    
        _hubConnection.Closed += HubConnectionOnClosed;
        _hubConnection.Reconnected += HubConnectionOnReconnected;
        _hubConnection.Reconnecting += HubConnectionOnReconnecting;
        
        await _hubConnection.StartAsync()
        await base.OnInitializedAsync();
    }   
    
    private async Task<string> GetAccessToken()
    {
        var tokenResult = await AccessTokenProvider.RequestAccessToken(...)
        // etc...
    }
    
    // .. Event Handlers
    
    public ValueTask DisposeAsync()
    {
        _logger.LogInformation($"Disposing Hub: {_hubConnection.ConnectionId}");

        _hubConnection.Closed -= HubConnectionOnClosed;
        _hubConnection.Reconnected -= HubConnectionOnReconnected;
        _hubConnection.Reconnecting -= HubConnectionOnReconnecting;

        return _hubConnection.DisposeAsync();
    }
}

Previously I had it as an injected service but I eventually simplified it to this structure but it continues to get this error on reload. It's not every time I reload but most times.

I have tried changing the dispose pattern without success. I can't find any information on the error anywhere else.

Any ideas?

Upvotes: 3

Views: 1045

Answers (1)

Andrew H
Andrew H

Reputation: 905

I don't have a definitive answer as to the underlying reason but I suspect that this is a bug somewhere in the SignalR/dotnet framework resulting in the GCing of a delegate because something drops a reference to it.

One way I've managed to provoke this error reasonably consistently is to have a handler returning just a Task, e.g.

_hubConnection.On<TEvent>(eventType.Name, OnEvent);

where OnEvent looks like this:

// THIS IS THE BROKEN SIGNATURE - DO NOT USE
private async Task OnEvent<TEvent>(TEvent e)
{
}

A workaround which appears to have fixed it for me is to make the handler actually return something. This seems to make something deeper in the framework hold a reference for longer so that it doesn't get GC'ed. E.g.

// WORKS ON MY MACHINE - Note the return type of Task<object>
private async Task<object> OnEvent<TEvent>(TEvent e)
{
  // ... Do stuff
  return null;
}

Upvotes: 0

Related Questions