SSE transport times out behind IIS due to idle connection #835
Comments
I have tried this as illustrated in my question on SO: .Net Core SignalR - connection timeout - heartbeat timer - connection state change handling However, I don't manage to restart the connection after net::ERR_NETWORK_IO_SUSPENDED (e.g. when resuming after hibernation). What I've tried to do is var connection = null;
var heartBeatTockTimer;
registerSignalRConnection();
function registerSignalRConnection() {
if (connection !== null) {
try {
connection.stop().catch(err => console.log(err));
} catch (exception) {
console.log(exception);
}
}
connection = new signalR.HubConnection("/signalr", { transport: SignalR.TransportType.ServerSentEvents });
connection.on("Heartbeat", serverTime => { console.log(serverTime); });
connection.start().then(args => {
heartBeatTockTimer = setInterval(sendHeartBeatTock, 10000);
}).catch(args => console.log("Error connecting", args));
return connection.connection.connectionState; The client heartbeat then looks like this function sendHeartBeatTock() {
try {
console.log("Standard attempt HeartBeatTock");
connection.invoke("HeartBeatTock").catch(err => {
console.log("HeartbeatTock Standard Error", err);
setTimeout(function() {
registerSignalRConnection();
connection.invoke("HeartBeatTock").catch(err => {
console.log("Error after HeartbeatTock Restart", err);
clearInterval(heartBeatTockTimer);
});
}, 5000);
});
} catch (exception) {
console.log("caught exception on standard attempt", exception);
}
} Is there a better way to check/restore a connection when invoke fails? |
You did not show what error you get so I am just shooting in the dark - when you are starting after hibernation you may want to try a few start attempts with a timeout. |
The error occurs when connected to a Hub, then putting the computer to sleep. When resuming, you'd get "POST https://localhost:44311/signalr?id=df54e3b8-e395-4a2f-89f4-8345bb0df99c net::ERR_NETWORK_IO_SUSPENDED" in signalr-client-1.0.0-alpha2-funal.js:159 At this point, I wanted to wait for 10 seconds and try again, but it seems I'm getting confused in how to set this up properly due to the asynchronous nature of the library. Beginner, sorry! This is what I am trying: var connection = null;
var heartBeatTockTimer; // It's named tock as opposed to tick from the server...
var retryAttempts = 5;
var retryWaitSeconds = 10;
var heartBeatTockTimerSeconds = 10;
// helper function to delay execution
$.wait = function(miliseconds) {
var defer = $.Deferred();
setTimeout(function() { defer.resolve(); }, miliseconds);
return defer;
};
registerSignalRConnection();
function registerSignalRConnection() {
if (connection !== null) {
console.log("registerSignalRConnection was not null", connection);
connection.stop().catch(err => console.log(err));
}
console.log("Creating new connection");
connection = new signalR.HubConnection("/signalr", { transport: signalR.TransportType.ServerSentEvents });
connection.start().then(() => {
console.log("Connection started, starting timer.");
heartBeatTockTimer = setInterval(sendHeartBeatTock, heartBeatTockTimerSeconds * 1000);
return connection;
}).catch(exception => {
console.log("Error connecting", exception, connection);
return null;
}); The function HeartBeatTock that is registered with the timer is supposed to detect when errors occur, and then try for a number of attempts to reconnect to the Hub. At some point I'd like to display a notification after the max number of retries are exceeded: function sendHeartBeatTock() {
console.log("Standard attempt HeartBeatTock");
connection.invoke("HeartBeatTock").then(() => { console.log("HeartbeatTock worked.")}).catch(err => {
console.log("HeartbeatTock Standard Error", err);
// retryInterval is the ID returned by setInterval to stop it once successful
let retryInterval = 0;
let attempts = 0;
retryInterval = setInterval(function () {
$.wait(retryWaitSeconds * 1000).then(function () {
++attempts;
console.log("executing attempt #" + attempts.toString());
// I tried to return connection from registerSignalRConnection() in order to use .then(), but that didn't quite work out. Lost here now... and if it returned null it'd need checking I guess - where?
registerSignalRConnection().then(() => {
connection.invoke("HeartBeatTock").then(() => { console.log("HeartbeatTock worked, Attempt #".attempts.toString()) }).catch(err => {
console.log("Error after HeartbeatTock Restart #" + attempts.toString(), err);
clearInterval(heartBeatTockTimer);
});
});
// success or give up?
console.log("Connection: ", connection.connection.state, "attempt: ", attempts);
if (connection.connection.state == 2 || attempts > retryAttempts) {
clearInterval(retryInterval);
}
});
}, retryWaitSeconds * 1000);
});
} Because this all invoked and deferred, I think I'm resetting attempts and timers all the time. The main purpose is achieved - the connection is kept alive forever, but I am unable to make it reconnect after the computer went to sleep. What you are suggesting is what I am trying to do, but seem unable to figure out how. I did notice that a few issues appeared regarding Websockets behind IIS and the timeouts of SSE, so I thought this may help others as well - would be nice to see an example in a sample. |
I think I have figured out a solution now. My error above was that I was trying to re-register the connection at the same time (or with the same delay) as the next invocation of sendHeartBeat was running; essentially creating an error loop. |
We have keep alives, this should fix things. |
After 120 seconds of no messages through the SSE transport it will be closed by IIS as an idle connection. Adding keep-alives could fix this.
The text was updated successfully, but these errors were encountered: