Event listeners on dynamic namespaces break when server emits on matching custom namespace before client connects
See original GitHub issueDescribe the bug
Generic description When a server emits an event on a custom namespace before any client has connected to that namespace, event listeners on a dynamic namespace that matches the custom namespace will not work. When a client connects to the custom namespace after an event has been emitted on it, the event listeners of the matching dynamic namespace will not trigger.
Our concrete use case We have a setup where we have a node cluster with multiple workers. Each worker is both used to communicate with clients using socket.io websockets and to run long running jobs on the background and emit progress about these jobs. The setup uses the Redis adapter to let socket.io communicate between workers.
We setup the “connect” event listeners on the serverside with a dynamic namespace, lets say /^\/dynamic-\d+$/
. Then, when a worker starts on a job it will start emitting on namespace dynamic-7
for instance.
We have experienced that the event listeners on the dynamic namespace do not work anymore for a particular worker when that worker starts emitting on a matching custom namespace before any client connects to the worker with the same custom namespace.
So, this works:
- Worker starts and initiates socket.io setup and adds listener to dynamic namespace
/^\/dynamic-\d+$/
- Clients connects to custom namespace
/dynamic-7
- Same worker starts working on long running job and emits on custom namespace
/dynamic-7
- Client receives updates
But this doesn’t:
- Worker starts and initiates socket.io setup and adds listener to dynamic namespace
/^\/dynamic-\d+$/
- Same worker starts working on long running job and emits on custom namespace
/dynamic-7
- Clients connects to custom namespace
/dynamic-7
- Client doesn’t receive updates
To Reproduce
Be sure to start the server script before starting the client script.
Socket.IO server version: 4.3.2
Server
const socketio = require("socket.io");
const io = new socketio.Server(3000, {});
io.of(/^\/dynamic-\d+$/).on("connection", (socket) => {
// we only get here if the bottom line is commented out, otherwise this listener is never triggered
console.log(`connected with ${socket.id} based on dynamic namespace`);
io.of("/dynamic-1").emit("some_event", {"foo": "bar"});
});
// this initial emit causes the bug. comment out this line to see the expected behavior
io.of("/dynamic-1").emit("some_event", {"foo": "bar"});
Socket.IO client version: 4.3.2
Client
const socketio = require("socket.io-client");
const socket = socketio.io("ws://localhost:3000/dynamic-1", {});
socket.on("connect", () => {
console.log(`connected with id ${socket.id}`);
});
// we will only receive this event if the server doesn't emit before we connect
socket.on("some_event", (data) => {
console.log("received event:", data);
});
Expected behavior I would expect the server to be able to start emitting events on a custom namespace that matches a dynamic namespace, even if no client has yet connected to that namespace.
Platform:
- Device: Chrome
- OS: Ubuntu
Additional context Note that for us the cluster setup caused the bug to arise, but it is not necessary as the reproduction example demonstrates.
Our current workaround is to execute io._checkNamespace("/dynamic-1", {}, () => {})
before we execute the first emit. This ensures that the custom namespace is added to the dynamic namespace and the event listeners of the dynamic namespace are copied to the custom namespace.
Issue Analytics
- State:
- Created 2 years ago
- Reactions:4
- Comments:8 (2 by maintainers)
Top GitHub Comments
@dknapp47 Yes, we are running multiple workers in our cluster. The workaround ensures that a child namespace on which a worker wants to broadcast (e.g.
/dynamic-1
) is first assigned to the dynamic namespace that was created for the listener (e.g./^\/dynamic-\d+$/
. So currently we are doing this check before any emit on a child namespace of a dynamic namespace.Well I would certainly make sense to register all middlewares / event handlers that were defined on the dynamic namespace level when .of() is called with a string and it matches a dynamic namespace, not only when the first connection is done. The current flow is confusing and unexpected. Especially in the multi instance (cluster) usecase where you want to emit event to a namespace even if there were no connections in your instance yet.
We will use the workaround from @thombohlk to match that behavior for now.