Dashboard "Lost connection" when in background tab

Yes (so far) Firefox is the only browser that exhibits this disconnect in the background problem.

OK, that is interesting. Good to know that I seem to have done something right though :slight_smile:

I've tried it myself now with the v2 branch of uibuilder and I can see that I don't get the disconnect/reconnect as you say. I also note that after leaving the Dashboard page running for a while (quite a long time), I did get the "Connection Lost" message. I wonder if there is a machine resources issue at work - I'm on an i7, 16GB RAM machine, don't know if that makes a difference.

@dceejay, there is clearly some difference in the way that socket.io is implemented between Dashboard and uibuilder. I have tried to optimise the use of socket.io connections but I'll be the first to admit that it was a lot of trial and error, it is a pig to work with.

@dceejay @TotallyInformation Sorry I am not expert then I am not sure to understand what you mean. My current understanding is that you suspect Firefox having a "unexpected" behavior by disconnecting the sockets when in background after a certain period, right ?

So I had a quick look at Firefox bugzilla database to check if an opened bug could explain the behavior but I found no evidence there. Then I did a quick test to check the behavior of socket.io in a background tab of Firefox but everything went fine and I had no disconnect with this simple test.
This simple test consists in having a socket opened between server and client side, server continuously sending a message over the socket and client displaying the content in a div element (see below for simple test).

So could you please elaborate a bit more about what is going wrong between Firefox and socket.io and may I expect a "fix" someday in the dashboard if it really comes from the way that socket.io is implemented in it ?

client.html:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <div id="test"></div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js"></script>
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
  <script src="./client.js"></script>
</body>

</html>

client.js

var address = "localhost";
var client = io("http://" + address + ":17001/");
client.on("message", function (e) {
    $("#test").text(e);
});

server.js

console.log("Server started");
var io = require("socket.io")(17001);
io.on("connection", function (socket) {
    var address = socket.request.connection.remoteAddress;
    console.log("Someone joined. Socket ID:", socket.id, address);
    setInterval(_=>{io.emit("message",Date())},10);
    socket.on("disconnect", function () {
        console.log("Someone disconnected!");
    });
});

The dashboard code is here - https://github.com/node-red/node-red-dashboard
the main client side connect handler is in https://github.com/node-red/node-red-dashboard/blob/master/src/main.js

Feel free to investigate (and if necessary raise a Pull Request). I simply don't have any more time to look at this when for me it's working as designed.

Holy c**p Dave, I've no idea how you follow that stuff!

On the server side, one difference is that, in uibuilder, I set up both a channel and a namespace in socket.io - don't know if that makes a difference at all?

You're right ! Having a look at uibuilder and dashboard, this is one of the main difference between both implementation. Don't know either if it could make a difference !? Also another difference is the reconnect mechanism on same NS present in the uibuilder front end part.

Yes, I looked at that but couldn't see that it was being triggered in this case.

Socket.io is a bit of a black art - to me anyway - and it took me a LOT of time, trial and error to get it working in a way that was robust and stable. I trawled through the very limited documentation multiple times and even some of the code trying to understand it enough. Unfortunately, I've not managed to find anything better. It is pretty critical to use something like socket.io though, especially if you need to traverse firewalls and networks. It also enables isolation between rooms and channels which is especially useful for uibuilder which allows multiple apps to be created.

again you are right, I double checked and it is not triggered.

Also, I set up both a channel and a namespace in Dashboard (as it is done in uibuilder) but w/o effect on connection lost ...
I am clueless now because I don't see any major difference on the way the socket implementation is done but I still don't understand why Dashboard disconnect while uibuilder not !

1 Like

Sorry, it is beyond me as well.

Have you tried one of the other Dashboard-like nodes? I wonder if they behave the same way.

All I can say is that I've tried to keep the interactions between Node-RED and uibuilder as simple as possible while trying to use the best features of Socket.IO in order to maintain traffic isolation and allow for future changes.

Yes I did. I tried with a simple ui_text node from Dashboard, and I got the same behavior.

To be honest, I have now some doubts the issue is coming from socket.io implementation.
Actually, If I comment the line $timeout(function () { handler(data); }, 0); in events.js file from Dashboard, I have no more disconnection, and the front end continues to receive the msg from server side through the socket (checked that by adding a console.log in socketHandler) even when the tab is in background for several minutes ... of course in this case the Dashboard is not refreshed anymore with incoming data, but then it's worth looking in this direction (angularjs behavior, $timeout behavior when in background ,... ).

Also it may explained why there is not such behavior with uibuilder and with my simple test applications.

1 Like

I'm afraid, I couldn't quite work out why the timeout was there. Given that Angular is responsive to data changes, simply updating the data should be enough shouldn't it? I'm obviously missing something.

Generally that expression is used to assign the processing to the next iteration of the event loop. Not sure why in this case but shouldn't really have anything to so with the 55 sec dropout we are seeing with Firefox.

OK, I'm showing my limited knowledge now but I thought that Node.js had a command for doing that? nextTick()?

They are subtly different (can't recall off top of head) and is this on browser (not node) side ? (Node also has setImmediate)

Haha, never a good idea to respond first thing in the morning! You are, as always, correct.

If only I understood more about why you'd want to do that. But as I don't, I haven't considered it in my own code :wink:

It has likely something related with how Firefox is throttling the $timeout when tabs are in the background.
When I change the Firefox flag dom.min_background_timeout_value from 1000 to 4ms, then there is no more disconnection.

1 Like

@dceejay @TotallyInformation I performed a simple patch in Dashboard to avoid using $timeout.
It seems working on my side and I have no more disconnection when tab is in the background.
The patch consists in

  • changing in events.js :
            var socketHandler = function (data) {
                    $timeout(function() {handler(data);},0)
            };

into

            var socketHandler = function (data) {
                    handler(data);
            };
  • adding $scope.$apply(); at the end of events.on callback in main.js to get the dahboard refreshed :
    events.on(function (msg) {
        var found;
        // console.log("events.on")
        if (msg.hasOwnProperty('msg') && msg.msg.templateScope === 'global') {
            found = findHeadOriginalEl(msg.id);
            if (found) {
                replaceHeadOriginalEl(found, msg.msg.template)
            }
            else {
                found = findHeadElAppended(msg.id);
                if (found) {
                    replaceHeadEl(found, msg.msg.template)
                }
                else {
                    return;
                }
            }
        }
        else {
            found = findControl(msg.id, main.menu);
            if (found === undefined) { return; }
            for (var key in msg) {
                if (msg.hasOwnProperty(key)) {
                    if (key === 'id') { continue; }
                    found[key] = msg[key];
                }
            }
            if (found.hasOwnProperty("me") && found.me.hasOwnProperty("processInput")) {
                found.me.processInput(msg);
            }
        }
        $scope.$apply();
    });

It is working for me but I don't know if it is a dirty patch or not.
Could you please advice and maybe tell me if it is worth raising a pull request with such patch ?

I can't really comment about the patch I'm afraid as I don't know the innards of Dashboard well enough nor do I remember enough Angular to be helpful.

All I can say is that this looks more like the approach I use with an event listener listening for new data and then updating a monitored data element in the framework.

1 Like

Thanks anyway I appreciate your feedback.

looks good.. can't see a problem with it (yet :slight_smile:
Happy to have a PR if you want - can back out later if needs be.
(or I can edit those two lines.. no problem)

1 Like