cleanupEmptyChildNamespaces releases a child namespace while a socket is in its middleware, and the cleanup unregisters a newer namespace by name
Maintainer thường phản hồi trong vòng 2 ngày
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 54/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- node.js, typescript
- Lĩnh vực
- backend, networking
Hướng nghiên cứu
Start in dist/parent-namespace.js at the cleanupEmptyChildNamespaces handler in ParentNamespace.createChild; trace how middleware sockets are tracked and how the _nsps entry is removed. Add regression coverage for a socket held in async middleware while another connects and disconnects. Done when broadcasts reach every connected socket and cleanup preserves the currently registered namespace; the payload does not name a test file.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Describe the bug
With cleanupEmptyChildNamespaces: true, a child namespace is released as soon as namespace.sockets.size === 0, even while another socket of that namespace is still in its (async) middleware. That socket then joins the released namespace object, while the next connection gets a new object under the same name. Two consequences:
- Broadcasting with
io.of(name).emit(...)reaches only the sockets of the newer object; the socket admitted into the released object receives nothing. - When the released object later empties, the cleanup deletes the
_nspsentry by name, which unregisters the newer object while its sockets are still connected;io.of(name)then creates yet another object, and broadcasts no longer reach any connected socket.
(Related but different: #4772, cleanup after a socket is rejected in middleware.)
To Reproduce
Socket.IO server version: 4.8.3
const http = require('http');
const { Server } = require('socket.io');
const { io } = require('socket.io-client');
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const once = (emitter, event) => new Promise((resolve) => emitter.once(event, resolve));
const httpServer = http.createServer();
const ioServer = new Server(httpServer, { cleanupEmptyChildNamespaces: true });
let releaseHandshake;
const handshakeGate = new Promise((resolve) => { releaseHandshake = resolve; });
let handshakes = 0;
ioServer.of(/^\/room-\w+$/).use(async (socket, next) => {
handshakes++;
if (handshakes === 2) await handshakeGate; // e.g. an auth lookup that takes a while
next();
});
httpServer.listen(0, async () => {
const url = `http://localhost:${httpServer.address().port}/room-a`;
const opts = { forceNew: true, transports: ['websocket'] };
const first = io(url, opts);
await once(first, 'connect');
const nspAtStart = ioServer._nsps.get('/room-a');
const held = io(url, opts); // its handshake waits in the middleware
await sleep(100);
first.disconnect(); // the only connected socket leaves: the namespace is released
await sleep(100);
console.log('1. namespace released while "held" is still in its middleware:',
ioServer._nsps.get('/room-a') !== nspAtStart);
releaseHandshake();
await once(held, 'connect'); // "held" joins the released namespace object
const third = io(url, opts);
await once(third, 'connect'); // a new namespace object is created under the same name
const received = { held: 0, third: 0 };
held.on('news', () => received.held++);
third.on('news', () => received.third++);
ioServer.of('/room-a').emit('news');
await sleep(200);
console.log('2. broadcast to of("/room-a") with two connected sockets:', received,
'(expected { held: 1, third: 1 })');
held.disconnect(); // the released object empties: cleanup deletes the _nsps entry by name
await sleep(200);
console.log('3. after "held" leaves, the namespace "third" is in is still registered:',
ioServer._nsps.get('/room-a') != null);
received.third = 0;
ioServer.of('/room-a').emit('news');
await sleep(200);
console.log('4. broadcast to of("/room-a") with "third" still connected:', { third: received.third },
'(expected { third: 1 })');
third.disconnect();
ioServer.close();
process.exit(0);
});
Output (socket.io 4.8.3, socket.io-client 4.8.3, Node.js 24):
1. namespace released while "held" is still in its middleware: true
2. broadcast to of("/room-a") with two connected sockets: { held: 0, third: 1 } (expected { held: 1, third: 1 })
3. after "held" leaves, the namespace "third" is in is still registered: false
4. broadcast to of("/room-a") with "third" still connected: { third: 0 } (expected { third: 1 })
Expected behavior
- A namespace with a socket still in its middleware (
_preConnectSockets) is not released, or a socket finishing its handshake joins the namespace object currently registered under that name. - The cleanup removes the
_nspsentry only if it still points to the object being released (if (this._nsps.get(name) === namespace) this._nsps.delete(name)).
Platform:
- Server: Node.js 24, macOS and Linux
Additional context
The release check is in ParentNamespace.createChild (dist/parent-namespace.js, the cleanupEmptyChildNamespaces handler on namespace.sockets.size === 0), which does not consider namespace._preConnectSockets. We work around it on our side by keeping per-namespace-object state instead of per-name state.
- Ngôn ngữ chính
- TypeScript
- Star
- 63.2k
- Fork
- 10.2k
- Merge trung bình
- 1 ngày 23 giờ
- Pull request đã merge (30 ngày)
- 4
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của socketio/socket.io
-
Raise Engine.IO minimum to 6.6.10 for published security fixesCó thể đã có người làm @harshit-exe-dev đã nhận 27 ngày trước. Đang mở
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 2 ngày
-
Can't set response headers in Server.handleUpgradeCó thể đã có người làm @MsfPablo đã nhận 88 ngày trước. Đang mởquestion
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
socketio/socket.io#5497 · 2 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
WebTransport ignores query parameters in URLCó thể đã có người làm @cyphercodes đã nhận 176 ngày trước. Đang mởneeds major release question
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
socketio/socket.io#5496 · 3 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
-
Direct Sockets APIĐang mởenhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 20/100
Maintainer thường phản hồi trong vòng 2 ngày
-
"Basic CRUD application" can't be started with Angular clientCó thể đã có người làm Có pull request liên kết đang mở hoặc đã được merge. Đang mởto triage
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 65/100
socketio/socket.io#5567 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 2 ngày
Tất cả issue của socketio/socket.io
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
WalletConnect/walletconnect-monorepo#7392 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 68/100
-
Absenderzeile über dem Anschriftfeld wird bei Angeboten und Rechnungen am Ende abgeschnittenĐang mởfehler
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 66/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
Azure/azure-functions-nodejs-extensions#180 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
bug good first issue help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
laaielichter/Threesmith#28 ·
Maintainer thường phản hồi trong vòng 1 ngày