Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET Core SignalR, Server timeoute / Reconnect issue

I have a SignalR hub written in my MVC solution, with a Javascript client connecting from the view.

The point of the connection is to receive changes for a wallboard from the server. This has to happen almost instantly and requires a lifetime connection, since the webpage is running on a screen without direct pc access.

So far the SignalR connection works for a couple of hours before it gives error.

The error I get is

Error: Connection disconnected with error 'Error: Server timeout elapsed without receiving a message form the server.'.
Failed to load resource: net::ERR_CONNECTION_TIMED_OUT
Warning: Error from HTTP request. 0:
Error: Failed to complete negotiation with the server: Error
Error: Failed to start the connection: Error

Uncaught (in promise) Error
    at new HttpError (singlar.js:1436)
    at XMLHttpRequest.xhr.onerror (singalr.js:1583)

My client code

 let connection = new signalR.HubConnectionBuilder()
    .withUrl("/wbHub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

connection.start().then(function () {
    connection.invoke("GetAllWallboards").then(function (wallboard) {
        for (var i = 0; i < wallboard.length; i++) {
            displayWallboard(wallboard[i]);
        }
        startStreaming();
    })
})

connection.onclose(function () {
    connection.start().then(function () {
            startStreaming();
    })
})

function startStreaming() {
    connection.stream("StreamWallboards").subscribe({
        close: false,
        next: displayWallboard
    });
}

Hub Code:

public class WallboardHub : Hub
{
    private readonly WallboardTicker _WallboardTicker;

    public WallboardHub(WallboardTicker wallboardTicker)
    {
        _WallboardTicker = wallboardTicker;
    }

    public IEnumerable<Wallboard> GetAllWallboards()
    {
        return _WallboardTicker.GetAllWallboards();
    }

    public ChannelReader<Wallboard> StreamWallboards()
    {
        return _WallboardTicker.StreamWallboards().AsChannelReader(10);
    }

    public override async Task OnConnectedAsync()
    {
        await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
        await base.OnConnectedAsync();
    }

    public override async Task OnDisconnectedAsync(Exception exception)
    {
        await Groups.RemoveFromGroupAsync(Context.ConnectionId, "SignalR Users");
        await base.OnDisconnectedAsync(exception);
    }
}

Question 1: Is the way I handle reconnecting correct? From the error it feels like the .onclose works, but that it only tries one time? Is there anyway to try for x min before showing error?

Question 2: Reloading the website makes the connection work again, is there potential anyway to refresh the browser on signalR connection error?

like image 247
Casper Avatar asked Jul 05 '18 06:07

Casper


3 Answers

ASP.NET Core 2.1 (current LTS release) with the corresponding SignalR release doesn't seem to have some integrated reconnecting method avaliable. The code from @Shidarg doesn't work for me, it calls the reconnect method in a infinitive loop crashiny my browser. I also like the async/await syntax from C# more, so I updated it:

let reconnectWaitTime = 5000
let paramStr = '?myCustomArg=true'
let client = new signalR.HubConnectionBuilder()
    .withUrl("/overviewHub" + paramStr)
    .build();

client.onclose(async () => {
    console.warn(`WS connection closed, try reconnecting with loop interval ${reconnectWaitTime}`)
    tryReconnect(client)
})
await tryReconnect(client)

async function tryReconnect(client) {
    try {
        let started = await client.start()
        console.log('WS client connected!')

        // Here i'm initializing my services, e.g. fetch history of a chat when connection got established

        return started;
    } catch (e) {
        await new Promise(resolve => setTimeout(resolve, reconnectWaitTime));
        return await tryReconnect(client)
    }
}

But for ASP.NET Core 3 they included a reconnecting method:

let client = new signalR.HubConnectionBuilder()
    .withUrl("/myHub")
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Information)
    .build();

Per default it try three reconnects: First after 2 seconds, second after 10 seconds and the last about 30 seconds. This could be modificated by passing the intervalls as array parameter:

.withAutomaticReconnect([5000, 1500, 50000, null])

This example re-trys after 5s, 15s and 50s. The last null param tell SignalR to stop re-trying. More information could be found here: https://www.jerriepelser.com/blog/automatic-reconnects-signalr/

like image 62
Lion Avatar answered Oct 29 '22 10:10

Lion


Configuring automatic reconnects only requires a call to withAutomaticReconnect on the HubConnectionBuilder. Here is what my JavaScript code looks like for configuring my connection:

connection = new signalR.HubConnectionBuilder()
.withUrl("/publish-document-job-progress")
.withAutomaticReconnect()
.configureLogging(signalR.LogLevel.Information)
.build();

You can configure the backoff period by passing an array of retry delays to the call to withAutomaticReconnect(). The default for this is [0, 2000, 10000, 30000, null]. The null value tells SignalR to stop trying. So, for example, if I wanted it to retry at 0, 1 second and 5 seconds, I can configure my HubConnectionBuilder as follows:

connection = new signalR.HubConnectionBuilder()
.withUrl("/publish-document-job-progress")
.withAutomaticReconnect([0, 1000, 5000, null])
.configureLogging(signalR.LogLevel.Information)
.build();
like image 45
mincasoft Avatar answered Oct 29 '22 09:10

mincasoft


I have the same issue (Question 1), and i resolve with this:

const connection = new SignalR.HubConnectionBuilder()
    .withUrl("/hub")
    .configureLogging(SignalR.LogLevel.Information)
    .build();

connect(connection);

async function connect(conn){
    conn.start().catch( e => {
        sleep(5000);
        console.log("Reconnecting Socket");
        connect(conn);  
    }
    )
}

connection.onclose(function (e) {
            connect(connection);
    });

  async function sleep(msec) {
  return new Promise(resolve => setTimeout(resolve, msec));
}

Every 5 seconds tries to reconnect, but i don't know if this is the right way to do this.

like image 16
Tasio Avatar answered Oct 29 '22 11:10

Tasio