From 9d86397243bcbb5775a29d96e5ef03e17148a8e7 Mon Sep 17 00:00:00 2001 From: Sebastiaan Marynissen Date: Fri, 22 Oct 2021 19:09:00 +0200 Subject: [PATCH] fix: fix race condition in dynamic namespaces (#4137) Using an async operation with `io.use()` could lead to the creation of several instances of a same namespace, each of them overriding the previous one. Example: ```js io.use(async (nsp, auth, next) => { await anOperationThatTakesSomeTime(); next(); }); ``` Related: https://github.com/socketio/socket.io/pull/4136 --- lib/client.ts | 1 - lib/index.ts | 19 +++++++++++-------- test/socket.io.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index 268cfbd085..8b9db02532 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -116,7 +116,6 @@ export class Client< | false ) => { if (dynamicNspName) { - debug("dynamic namespace %s was created", dynamicNspName); this.doConnect(name, auth); } else { debug("creation of namespace %s was denied", name); diff --git a/lib/index.ts b/lib/index.ts index 72c87ef677..53ee552a9e 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -202,15 +202,18 @@ export class Server< } nextFn.value(name, auth, (err, allow) => { if (err || !allow) { - run(); - } else { - const namespace = this.parentNsps - .get(nextFn.value)! - .createChild(name); - // @ts-ignore - this.sockets.emitReserved("new_namespace", namespace); - fn(namespace); + return run(); } + if (this._nsps.has(name)) { + // the namespace was created in the meantime + debug("dynamic namespace %s already exists", name); + return fn(this._nsps.get(name) as Namespace); + } + const namespace = this.parentNsps.get(nextFn.value)!.createChild(name); + debug("dynamic namespace %s was created", name); + // @ts-ignore + this.sockets.emitReserved("new_namespace", namespace); + fn(namespace); }); }; diff --git a/test/socket.io.ts b/test/socket.io.ts index 4777796f89..63e7f1b830 100644 --- a/test/socket.io.ts +++ b/test/socket.io.ts @@ -15,6 +15,8 @@ import { io as ioc, Socket as ClientSocket } from "socket.io-client"; import "./support/util"; import "./utility-methods"; +type callback = (err: Error | null, success: boolean) => void; + // Creates a socket.io client for the given server function client(srv, nsp?: string | object, opts?: object): ClientSocket { if ("object" == typeof nsp) { @@ -998,6 +1000,46 @@ describe("socket.io", () => { const socket = client(srv, "/dynamic-101"); }); }); + + it("should handle race conditions with dynamic namespaces (#4136)", (done) => { + const srv = createServer(); + const sio = new Server(srv); + const counters = { + connected: 0, + created: 0, + events: 0, + }; + const buffer: callback[] = []; + sio.on("new_namespace", (namespace) => { + counters.created++; + }); + srv.listen(() => { + const handler = () => { + if (++counters.events === 2) { + expect(counters.created).to.equal(1); + done(); + } + }; + + sio + .of((name, query, next) => { + buffer.push(next); + if (buffer.length === 2) { + buffer.forEach((next) => next(null, true)); + } + }) + .on("connection", (socket) => { + if (++counters.connected === 2) { + sio.of("/dynamic-101").emit("message"); + } + }); + + let one = client(srv, "/dynamic-101"); + let two = client(srv, "/dynamic-101"); + one.on("message", handler); + two.on("message", handler); + }); + }); }); });