Skip to content

Commit

Permalink
fix: fix race condition in dynamic namespaces (#4137)
Browse files Browse the repository at this point in the history
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: #4136
  • Loading branch information
sebamarynissen authored and darrachequesne committed Oct 24, 2021
1 parent 44e20ba commit 9d86397
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 9 deletions.
1 change: 0 additions & 1 deletion lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 11 additions & 8 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
};

Expand Down
42 changes: 42 additions & 0 deletions test/socket.io.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
});
});
});
});

Expand Down

0 comments on commit 9d86397

Please sign in to comment.