Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SignalR Does not auto-reconnect

I have a SignalR Hub and a .net Xamarin Android client. When a stable internet connection is used they work fine, the client can send to the hub and the hub can send to the client.

However the client appears to have no ability to automatically reconnect (contrary to what the online documentation says) and trying to implement reconnect myself is impossible because of the following issue.

If the client succesfully starts the HubConnection the State goes from Disconnected -> Connecting -> Connected. But if you pull the network cable from the client so there is no way for it to get to the server. The state never (and I do mean never) changes from "Connected".

I do get a single "ConnectionSlow" event, but the connections State never changes and calling Start on the connection does nothing because looking at the signalR source code, internally it checks if the State != "Connected" and if it isnt, it just returns. SignalR Github Source Code

My question is, how are you meant establish a SignalR connection to a server and have it (or implement) automatic reconnect. Because nothing I have tried works.

like image 561
Kyro Avatar asked Nov 10 '22 00:11

Kyro


1 Answers

I´m not really sure if this will help you, but just be aware that:

  1. After signalr disconnects (i.e: network disconnection) I found out it´s better to create a new connection hub. Don´t expect signalr to reconnect always (it will try for a few seconds, then bye bye connection).

  2. If you want to dismiss the current connection, don´t try to close it (signalR could freeze for around 20 seconds before it actually releases the connection. Just reasign the connection variable to a new one.

This is my working code:

    private async Task Connect(bool dismissCurrentConnection = false)
    {
        // Always create a new connection to avoid SignalR close event delays
        if (connection != null)
        {
            if (!dismissCurrentConnection)
            {
                return;
            }

            connection.StateChanged -= OnConnectionStateChangedHandler;
            connection.Reconnected -= OnReconnectedHandler;
            // DON´T call connection.Dispose() or it may block for 20 seconds
            connection = null;
            proxy = null;
        }

        connection = new HubConnection(Settings.SERVER);
        // connection.TransportConnectTimeout = TimeSpan.FromSeconds(5);
        connection.TraceWriter = tracer;
        connection.TraceLevel = TraceLevels.All;
        connection.StateChanged += OnConnectionStateChangedHandler;
        connection.Reconnected += OnReconnectedHandler;

        proxy = connection.CreateHubProxy("ChatHub");
        proxy.On<ChatMessage>("AddNewMessage", message => AddNewMessage(message));
        proxy.On<ChatMessage>("ConfirmMessageDelivered", Message => ConfirmMessageDelivered(Message));
        proxy.On<string>("ConfirmMessageReceived", uid => ConfirmMessageReceived(uid));
        proxy.On<string>("ConfirmMessageRead", uid => ConfirmMessageRead(uid));
        proxy.On<UserChatStatus>("ChangeUserChatStatus", status => ChangeUserChatStatus(status));

        if (connection.State == ConnectionState.Disconnected)
        {
            try
            {
                MvxTrace.Trace("[{0}] Connecting...", nameof(ChatService));
                await connection.Start();
                await invokeQueue.ProcessQueue();
            }
            catch (Exception ex)
            {
                MvxTrace.Error("[{0}] CONNECTION START ERROR: {1}", nameof(ChatService), ex.Message);
            }
        }
    }

The first time I will connect with await Connect(); Subsequent connections will use await Connect(true) to dismiss the current one

    private void OnReconnectedHandler()
    {
        Task.Factory.StartNew(async () => await invokeQueue.ProcessQueue());
    }

    private void OnConnectionStateChangedHandler(StateChange change)
    {
        this.ConnectionState = change.NewState;
        OnConnectionStateChanged?.Invoke(change);

        switch (change.NewState)
        {
            case ConnectionState.Disconnected:
                // SignalR doesn´t do anything after disconnected state, so we need to manually reconnect
                reconnectTokenSource = new CancellationTokenSource();
                Task.Factory.StartNew(async () =>
                {
                    await Task.Delay(TimeSpan.FromSeconds(Settings.RECONNECT_PERIOD_SECONDS), reconnectTokenSource.Token);
                    await Connect(true);
                }, reconnectTokenSource.Token);

                break;
        }
    }

In case you wonder what is "invokeQueue", it´s just a custom utility that catches all signalr proxy calls when state is not connected and process them once connection is back.

"reconnectTokenSource" will be cancelled in case I don´t want the re-connect process to go on (i.e: when closing the activity).

It looks like we both have very similar configurations. The only obvious difference is that you´re using the nuget in an Android project and I´m using it on a PCL.

In this log you can check disconnection events:

mvx:Diagnostic: 14,72 [SignalRTracer] 01:47:56.2406950 - null - ChangeState(Disconnected, Connecting)
mvx:Diagnostic: 15,48 [SignalRTracer] 01:47:57.0176730 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - SSE: GET http://192.168.0.53/signalr/connect?clientProtocol=1.4&transport=serverSentEvents&connectionData=[{"Name":"ChatHub"}]&connectionToken=IgMS6rxmjHJCeudFlDRV8jFSt9Tz1Mt210X21RbiFAIalj%2BioMMRWPH5%2BfNaNIkVW%2BzHbv%2FmSjk8uoRNVbK%2FKUL%2FI87iBkejKkXYivq5FZy6x8E1%2FAK2rBnCg3Zi9nwY&noCache=d4878c0b-5c2a-4535-929c-29952f5e6795
mvx:Diagnostic: 15,56 [SignalRTracer] 01:47:57.0945740 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - SSE: OnMessage(Data: initialized)
mvx:Diagnostic: 15,57 [SignalRTracer] 01:47:57.1054290 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - SSE: OnMessage(Data: {"C":"d-3050D8AB-B,C|I,0|J,1","S":1,"M":[]})
mvx:Diagnostic: 15,70 [SignalRTracer] 01:47:57.2408050 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - ChangeState(Connecting, Connected)
mvx:Diagnostic: 15,82 [SignalRTracer] 01:47:57.3586480 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - SSE: OnMessage(Data: {"C":"d-3050D8AB-B,D|I,0|J,1","M":[]})
mvx:Diagnostic: 15,83 [SignalRTracer] 01:47:57.3718630 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - OnMessage({"I":"0"})

Then I switched off wifi:

mvx:Diagnostic: 29,55 [SignalRTracer] 01:48:11.0813880 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - OnError(System.Net.Sockets.SocketException: Connection timed out at System.Net.Sockets.Socket.EndReceive (IAsyncResult result) [0x0002d] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/mcs/class/System/System.Net.Sockets/Socket.cs:1798 at System.Net.Sockets.NetworkStream.EndRead (IAsyncResult ar) [0x0002f] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/mcs/class/System/System.Net.Sockets/NetworkStream.cs:320 )
mvx:Diagnostic: 31,56 [SignalRTracer] 01:48:13.1037440 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - ChangeState(Connected, Reconnecting)
mvx:Diagnostic: 31,68 [SignalRTracer] 01:48:13.2199720 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - OnError(System.Net.Sockets.SocketException: Network is unreachable at System.Net.Sockets.Socket.Connect (System.Net.EndPoint remoteEP) [0x000bc] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/mcs/class/System/System.Net.Sockets/Socket.cs:1235 at System.Net.WebConnection.Connect (System.Net.HttpWebRequest request) [0x0019b] in /Users/builder/data/lanes/2098/3efa14c4/source/mono/mcs/class/System/System.Net/WebConnection.cs:213 )

Later on...

mvx:Diagnostic: 61,68 [SignalRTracer] 01:48:43.2182200 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - OnError(System.TimeoutException: Couldn't reconnect within the configured timeout of 00:00:30, disconnecting.)
mvx:Diagnostic: 61,69 [SignalRTracer] 01:48:43.2330650 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - Disconnected
mvx:Diagnostic: 61,76 [SignalRTracer] 01:48:43.2972460 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - Transport.Dispose(6dd879af-7d49-4632-96b2-cb5fb542dc24)
mvx:Diagnostic: 61,77 [SignalRTracer] 01:48:43.3065810 - 6dd879af-7d49-4632-96b2-cb5fb542dc24 - Closed
like image 168
xleon Avatar answered Nov 23 '22 23:11

xleon