Skip to content

Server is not cleaning up namespaces after client is rejected in middleware. #4772

@carera

Description

@carera

Hello team! 💜

Describe the bug
When using SocketIO with adapter (namely redis adapter in my case) and dynamic namespaces, the namespace isn't cleaned up when SocketIO server throws an error in middleware.

I believe this is a continuation of a fix @stevebaum23 previously made here: #4602
The new flag cleanupEmptyChildNamespaces is cleaning up namespaces when last client disconnects gracefully. It is however not cleaning up properly when client is rejected in middleware.

To Reproduce

Socket.IO server version: 4.7.1

Server

import { Server } from "socket.io";
import Redis from "ioredis";
import { RedisAdapter } from "@socket.io/redis-adapter";

const pubClient = new Redis({
  host: "localhost",
  port: 6370,
});
const subClient = pubClient.duplicate();

function redisAdapterFactory(nsp) {
  return new RedisAdapter(nsp, pubClient, subClient);
}

const io = new Server({
  transports: ["websocket"],
  adapter: redisAdapterFactory,
  cleanupEmptyChildNamespaces: true,
});

const ns = io.of(/\/path.*/);

ns.use((fn, next) => {
  if (Math.random() > 0.5) {
    next(new Error("not allowed!"));
  } else {
    next();
  }
});

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

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

io.listen(3000);

Socket.IO client version: 4.7.1

Client

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

const socket = io("ws://localhost:3000/path/abc", {
  transports: ["websocket"],
});

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

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

socket.on("error", (err) => {
  console.log(err, "error");
});

socket.on("connect_error", (err) => {
  console.log(err, "connect_error");
});

Expected behavior

In the server code, I have a middleware that allows the client to connect 50% of the time (at random). The client's only job is to connect and disconnect straight away.
When observed on redis (using e.g. redis-cli and MONITOR command), when client connects and gracefully disconnects, the Redis namespace is cleaned up (we observe subscribe and unsubscribe events):

"psubscribe" "socket.io#/path/abc#*"
"subscribe" "socket.io-request#/path/abc#" "socket.io-response#/path/abc#" "socket.io-response#/path/abc#jhkYJI#"
"punsubscribe" "socket.io#/path/abc#*"
"unsubscribe" "socket.io-request#/path/abc#" "socket.io-response#/path/abc#" "socket.io-response#/path/abc#jhkYJI#"

when however the server rejects the client in middleware using next(new Error('not allowed!')), the redis subscription is made, but is never cleaned up:

"psubscribe" "socket.io#/path/abc#*"
"subscribe" "socket.io-request#/path/abc#" "socket.io-response#/path/abc#" "socket.io-response#/path/abc#sZeoKO#"

What seems to be happening is that when we call next(new Error('not allowed!') in a socketIO middleware (as suggested in docs), socketIO lib calls _cleanup on error here
Note that, at this point, redis-adapter is already subscribed to topics in Redis. Thus I believe a cleanup similar to the one in _onclose should happen.

Are you able to assist, please?

Platform:

  • Device: MacBook Pro
  • OS: MacOS Ventura 13.4.1

Additional context
Add any other context about the problem here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions