Skip to content

Heartbeat detection is delayed when timers throttled, and messages can be sent over expired connection #5135

Closed
@tjenkinson

Description

@tjenkinson

Describe the bug

Due to chrome (and maybe other browsers) throttling timers when the device sleeps, the heartbeat detection logic doesn't always run in time, causing messages to be lost before reconnection happens late.

To Reproduce

Socket.IO server version: 4.7.5

Server

import { Server } from "socket.io";

const io = new Server(3000, { pingTimeout: 5_000, pingInterval: 25_000});

io.on("connection", (socket) => {
  console.log(`connect ${socket.id}`);

  socket.on("disconnect", () => {
    console.log(`disconnect ${socket.id}`);
  });
});

Socket.IO client version: 4.7.5

Client

import { io } from "socket.io-client";

// use a tunnel like the ports feature in vscode to make the connection work over the internet and use that host here
const socket = io("ws://<replace with host>:3000/", {});

socket.on("connect", () => {
  console.log(`connect ${socket.id}`, new Date());
});

socket.on("disconnect", () => {
  console.log("disconnect", new Date());
});

Expected behavior

  • Load the page and wait for the connected message
  • Close the laptop with it unplugged so that it goes to sleep
  • Wait 60 seconds or so to make sure it's gone to sleep for a bit
  • Open the laptop and go back to the page
  • The latest connection should not have received a ping in the last 30 seconds, and therefore it should be closed. But because chrome froze the timer that's going to do the disconnection it will take a while longer before that actually happens
  • If a message is sent before the reconnection it will probably go into the void and be lost

Proposed fixes

Check if connection is expired before sending every message

I opened a PR for this here: #5134

Let me know what you think

This doesn't cause the reconnect to happen immediately when the page is woken up, but it does mean attempting to send a message should trigger the reconnection, so outgoing messages shouldn't get lost.

Use setInterval and periodically check if a ping has not been received instead of setTimeout

I think this and storing the Date.now() of the last ping should be more accurate, and cause the reconnection to happen almost immediately when the page is woken up. Can also do the same on a visibilitychange event when the page goes back to visible.

Can look into a PR for this if you think it makes sense?

Platform:

  • Device: Mac
  • Browser: Chrome
  • OS: Mac OS 14.5 (23F79)

Additional context

#3507 (comment) is another fix in the past related to throttled timers

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions