socket.io-postgres-adapter icon indicating copy to clipboard operation
socket.io-postgres-adapter copied to clipboard

serverSideEmit(..., callback) does not deliver events until heartbeat is triggered

Open czPechy opened this issue 9 months ago • 0 comments

Environment

  • @socket.io/postgres-adapter: 0.4.0
  • socket.io: 4.8.x
  • pg: 8.16.x
  • Node.js: 22
  • PostgreSQL: 14.x

Problem description

When using serverSideEmit(..., callback), the callback is invoked with:

(err, responses) => {
  console.log(err);       // null
  console.log(responses); // [] ← empty
}

The event is never delivered to any other server, and the handler (io.on(...)) is never triggered on them.

However, if each individual server sends at least one serverSideEmit() (even a dummy one), things start working — the events are delivered and acknowledged as expected.

Note: If serverSideEmit() is used without a callback, the event is delivered to other servers even before they have sent any emits themselves. The issue only affects the ACK-based communication (serverSideEmit(..., callback)).


Root cause

The Postgres adapter does not deliver serverSideEmit(..., callback) events to other servers unless those servers have already started sending their own heartbeats.

Until a server sends a serverSideEmit():

  • It is connected and listening on the correct LISTEN channel
  • It can receive broadcast events without callback
  • But it will not be discoverable by other servers for ACK-based communication (serverSideEmit(..., callback))

As a result:

  • The initiating server sends a request and waits for ACKs
  • But other servers ignore the request because they don't recognize the source as active
  • The callback returns with responses = []

Minimal reproduction

// on all servers:
io.on("test-ack", (data, callback) => {
  console.log("received:", data);
  callback("ack from " + process.pid);
});

// on emitter server:
setTimeout(() => {
  io.serverSideEmit("test-ack", "hello", (err, responses) => {
    console.log("err:", err);           // null
    console.log("responses:", responses); // [] if heartbeat not yet started on other servers
  });
}, 1000);

Workaround

Ensure that every server sends a dummy serverSideEmit() after startup to trigger the heartbeat:

io.serverSideEmit("__init__", "poke");

This ensures each server becomes discoverable by others and can participate in ACK-based messaging.

czPechy avatar Jul 09 '25 11:07 czPechy