0

I wrote what I thought was a relatively simple websocket app for a home project, and it mostly works, but if I leave the page idle for too long (a few days, multiple sleep/wake cycles of the computer) it starts to consume 100% CPU and large amounts of memory (I've seen as much as 11GB). Is there something I'm doing obviously wrong, or how should I go about debugging this?

<html>
<head>
    <title>Websocket Test</title>
    <script>
        var SOCKET = null;
        function startSocket() {
            SOCKET = new WebSocket('wss://[REDACTED]');
            SOCKET.addEventListener('open', function(event) {
                document.getElementById('disconnected-overlay').style.display = 'none';
            });
            SOCKET.addEventListener('close', function(event) {
                console.log("websocket closed: " + event.code + " (" + event.reason + ")");
                document.getElementById('disconnected-overlay').style.display = 'block';
                setTimeout(startSocket, 5000);
            });
            SOCKET.addEventListener('error', function(event) {
                console.log("websocket error: " + event);
                document.getElementById('disconnected-overlay').style.display = 'block';
                setTimeout(startSocket, 5000);
            });
        }
        function mainSetup() {
            startSocket();
        }
        setInterval(function() {
            console.log("4 minutes");
            if (SOCKET.readyState === WebSocket.OPEN){
                SOCKET.send(JSON.stringify({
                    "user_name": "debugging",
                    "change": { "AddMinutes": 0 },
                }));
            } else {
                console.log("socket was not open");
            }
        }, 240000);
    </script>
</head>
<body onload="mainSetup()">
    <div id="disconnected-overlay">Disconnected</div>
</body>
</html>

I'm currently using Firefox 111.0.1. I haven't tested it on chrome/ium yet but I might try that soon. My best guess is the timeouts or callbacks from the setInterval are somehow accumulating while the page is idle and then all firing all at once, but I couldn't find any documentation of that as expected behavior.

Violet
  • 507
  • 4
  • 15
  • What do you see in the prints? – Edgar Barber Apr 14 '23 at 03:29
  • Does this answer your question? [Does JavaScript setInterval() method cause memory leak?](https://stackoverflow.com/questions/14034107/does-javascript-setinterval-method-cause-memory-leak) – Christopher Apr 14 '23 at 03:32
  • @EdgarBarber one time I checked there were a lot of successful connections and errors happening at back-to-back, although it's basically unresponsive when the bug happens and it's hard to even open the console to check. – Violet Apr 14 '23 at 03:39
  • @Christopher no I don't think so, was there something specific in that question that was relevant to this one? – Violet Apr 14 '23 at 03:41
  • Browsers can set pages to some kind of hibernation to reduce the average load when the tab is hidden, there are [lots of policies](https://developer.chrome.com/blog/background_tabs/) that can and will apply. Like automatically closing websocket connections and "delay" new connections until the tab gets active again, reducing timer/interval callbacks and when you reopen the page, after some time, it can happen that the callbacks are executed all at once causing high load and possibly high memory usage. – Christopher Apr 14 '23 at 03:45
  • how do other web pages use setInterval without unreasonable CPU usage if an arbitrary amount of events can be accumulated and fired all at once? Is there a way to say "no really, you have to wait at least 5s"? – Violet Apr 14 '23 at 03:55
  • You can add checks for the `document.visibilityState == "visible"` and a callback for the [visibilitychange](https://developer.mozilla.org/en-US/docs/Web/API/Document/visibilitychange_event) event. – Christopher Apr 14 '23 at 04:03
  • How can that ever be false if the callbacks are accumulated until the page is visible again? Also, where is this burst/catch-up behavior documented? – Violet Apr 14 '23 at 10:17
  • The info for chrome is already linked and firefox has similar feature ([Process Priority Manager](https://bugzilla.mozilla.org/show_bug.cgi?id=1548364)) added with version 69 -- Also callback functions as from [requestAnimationFrame()](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) (see 2nd paragraph) won't trigger when the tab isn't active. There are similar features in all major browsers to reduce the memory and cpu usage of background tabs. – Christopher Apr 14 '23 at 12:02
  • The linked page for chrome only describes throttling behavior, not anything like a burst/catch-up mode, and based on this (ancient) firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=376643 it's explicitly not supposed to work like that. Regardless, I think I figured out my own problem (as answered). – Violet Apr 14 '23 at 12:15

1 Answers1

1

I think I've figured it out; I have my reconnection timeout triggering in both onerror and onclose, whereas it should only be in onclose. Having it attempt to reconnect on both conditions means that each time there is an error it tries to reconnect twice - and then I have two websockets open so the next error event will cause 4 reconnection attempts. This leads to exponential growth each time there is an error event (like a connection reset due to computer sleep). After enough growth I'm probably running into a browser limit on the number of concurrent websocket connections, which makes the problem even worse since each new connection will immediately error.

Violet
  • 507
  • 4
  • 15