Skip to content

Commit 18f3fda

Browse files
committedMay 25, 2022
fix: prevent the socket from joining a room after disconnection
Calling `socket.join()` after disconnection would lead to a memory leak, because the room was never removed from the memory: ```js io.on("connection", (socket) => { socket.disconnect(); socket.join("room1"); // leak }); ``` Related: - #4067 - #4380
1 parent 5ab8289 commit 18f3fda

File tree

3 files changed

+77
-26
lines changed

3 files changed

+77
-26
lines changed
 

‎lib/namespace.ts

+28-24
Original file line numberDiff line numberDiff line change
@@ -218,34 +218,38 @@ export class Namespace<
218218
const socket = new Socket(this, client, query);
219219
this.run(socket, (err) => {
220220
process.nextTick(() => {
221-
if ("open" == client.conn.readyState) {
222-
if (err) {
223-
if (client.conn.protocol === 3) {
224-
return socket._error(err.data || err.message);
225-
} else {
226-
return socket._error({
227-
message: err.message,
228-
data: err.data,
229-
});
230-
}
221+
if ("open" !== client.conn.readyState) {
222+
debug("next called after client was closed - ignoring socket");
223+
socket._cleanup();
224+
return;
225+
}
226+
227+
if (err) {
228+
debug("middleware error, sending CONNECT_ERROR packet to the client");
229+
socket._cleanup();
230+
if (client.conn.protocol === 3) {
231+
return socket._error(err.data || err.message);
232+
} else {
233+
return socket._error({
234+
message: err.message,
235+
data: err.data,
236+
});
231237
}
238+
}
232239

233-
// track socket
234-
this.sockets.set(socket.id, socket);
240+
// track socket
241+
this.sockets.set(socket.id, socket);
235242

236-
// it's paramount that the internal `onconnect` logic
237-
// fires before user-set events to prevent state order
238-
// violations (such as a disconnection before the connection
239-
// logic is complete)
240-
socket._onconnect();
241-
if (fn) fn();
243+
// it's paramount that the internal `onconnect` logic
244+
// fires before user-set events to prevent state order
245+
// violations (such as a disconnection before the connection
246+
// logic is complete)
247+
socket._onconnect();
248+
if (fn) fn();
242249

243-
// fire user-set events
244-
this.emitReserved("connect", socket);
245-
this.emitReserved("connection", socket);
246-
} else {
247-
debug("next called after client was closed - ignoring socket");
248-
}
250+
// fire user-set events
251+
this.emitReserved("connect", socket);
252+
this.emitReserved("connection", socket);
249253
});
250254
});
251255
return socket;

‎lib/socket.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,8 @@ export interface Handshake {
112112
*/
113113
export type Event = [string, ...any[]];
114114

115+
function noop() {}
116+
115117
export class Socket<
116118
ListenEvents extends EventsMap = DefaultEventsMap,
117119
EmitEvents extends EventsMap = ListenEvents,
@@ -511,14 +513,24 @@ export class Socket<
511513
if (!this.connected) return this;
512514
debug("closing socket - reason %s", reason);
513515
this.emitReserved("disconnecting", reason);
514-
this.leaveAll();
516+
this._cleanup();
515517
this.nsp._remove(this);
516518
this.client._remove(this);
517519
this.connected = false;
518520
this.emitReserved("disconnect", reason);
519521
return;
520522
}
521523

524+
/**
525+
* Makes the socket leave all the rooms it was part of and prevents it from joining any other room
526+
*
527+
* @private
528+
*/
529+
_cleanup() {
530+
this.leaveAll();
531+
this.join = noop;
532+
}
533+
522534
/**
523535
* Produces an `error` packet.
524536
*

‎test/socket.io.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use strict";
22

3-
import { Server, Socket, Namespace } from "../lib";
3+
import { Server, Socket, Namespace } from "..";
44
import { createServer } from "http";
55
import fs = require("fs");
66
import { join } from "path";
@@ -11,6 +11,7 @@ import type { AddressInfo } from "net";
1111
import * as io_v2 from "socket.io-client-v2";
1212
import type { SocketId } from "socket.io-adapter";
1313
import { io as ioc, Socket as ClientSocket } from "socket.io-client";
14+
import { createClient } from "./support/util";
1415

1516
import "./support/util";
1617
import "./utility-methods";
@@ -2109,6 +2110,40 @@ describe("socket.io", () => {
21092110
});
21102111
});
21112112

2113+
it("should leave all rooms joined after a middleware failure", (done) => {
2114+
const io = new Server(0);
2115+
const client = createClient(io, "/");
2116+
2117+
io.use((socket, next) => {
2118+
socket.join("room1");
2119+
next(new Error("nope"));
2120+
});
2121+
2122+
client.on("connect_error", () => {
2123+
expect(io.of("/").adapter.rooms.size).to.eql(0);
2124+
2125+
io.close();
2126+
done();
2127+
});
2128+
});
2129+
2130+
it("should not join rooms after disconnection", (done) => {
2131+
const io = new Server(0);
2132+
const client = createClient(io, "/");
2133+
2134+
io.on("connection", (socket) => {
2135+
socket.disconnect();
2136+
socket.join("room1");
2137+
});
2138+
2139+
client.on("disconnect", () => {
2140+
expect(io.of("/").adapter.rooms.size).to.eql(0);
2141+
2142+
io.close();
2143+
done();
2144+
});
2145+
});
2146+
21122147
describe("onAny", () => {
21132148
it("should call listener", (done) => {
21142149
const srv = createServer();

0 commit comments

Comments
 (0)
Please sign in to comment.