diff --git a/lib/client.ts b/lib/client.ts index 268cfbd085..8dc02c1d02 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -113,10 +113,15 @@ export class Client< ( dynamicNspName: | Namespace - | false + | false, + race: boolean = false ) => { if (dynamicNspName) { - debug("dynamic namespace %s was created", dynamicNspName); + if (race) { + debug("dynamic namespace %s already created", dynamicNspName); + } else { + 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..f38819195f 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -188,7 +188,8 @@ export class Server< name: string, auth: { [key: string]: any }, fn: ( - nsp: Namespace | false + nsp: Namespace | false, + race?: boolean ) => void ): void { if (this.parentNsps.size === 0) return fn(false); @@ -203,6 +204,10 @@ export class Server< nextFn.value(name, auth, (err, allow) => { if (err || !allow) { run(); + } else if (this._nsps.has(name)) { + // See #4136. It's possible that in the meantime the namespace has + // already been created, so we'll have to handle this properly. + fn(this._nsps.get(name) as Namespace, true); } else { const namespace = this.parentNsps .get(nextFn.value)! 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); + }); + }); }); });