Skip to content

Commit 50220f4

Browse files
feat(sharded): add an option for dynamic private channels (#526)
Related: #524
1 parent dc1407f commit 50220f4

File tree

2 files changed

+52
-12
lines changed

2 files changed

+52
-12
lines changed

Diff for: lib/sharded-adapter.ts

+30-12
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,17 @@ export interface ShardedRedisAdapterOptions {
3434
* The default value, useful when some rooms have a low number of clients (so only a few Socket.IO servers are notified).
3535
*
3636
* Only public rooms (i.e. not related to a particular Socket ID) are taken in account, because:
37-
*
3837
* - a lot of connected clients would mean a lot of subscription/unsubscription
3938
* - the Socket ID attribute is ephemeral
4039
*
40+
* - "dynamic-private"
41+
*
42+
* Like "dynamic" but creates separate channels for private rooms as well. Useful when there is lots of 1:1 communication
43+
* via socket.emit() calls.
44+
*
4145
* @default "dynamic"
4246
*/
43-
subscriptionMode?: "static" | "dynamic";
47+
subscriptionMode?: "static" | "dynamic" | "dynamic-private";
4448
}
4549

4650
/**
@@ -89,17 +93,18 @@ class ShardedRedisAdapter extends ClusterAdapter {
8993
SSUBSCRIBE(this.subClient, this.channel, handler);
9094
SSUBSCRIBE(this.subClient, this.responseChannel, handler);
9195

92-
if (this.opts.subscriptionMode === "dynamic") {
96+
if (
97+
this.opts.subscriptionMode === "dynamic" ||
98+
this.opts.subscriptionMode === "dynamic-private"
99+
) {
93100
this.on("create-room", (room) => {
94-
const isPublicRoom = !this.sids.has(room);
95-
if (isPublicRoom) {
101+
if (this.shouldUseASeparateNamespace(room)) {
96102
SSUBSCRIBE(this.subClient, this.dynamicChannel(room), handler);
97103
}
98104
});
99105

100106
this.on("delete-room", (room) => {
101-
const isPublicRoom = !this.sids.has(room);
102-
if (isPublicRoom) {
107+
if (this.shouldUseASeparateNamespace(room)) {
103108
SUNSUBSCRIBE(this.subClient, this.dynamicChannel(room));
104109
}
105110
});
@@ -109,10 +114,12 @@ class ShardedRedisAdapter extends ClusterAdapter {
109114
override close(): Promise<void> | void {
110115
const channels = [this.channel, this.responseChannel];
111116

112-
if (this.opts.subscriptionMode === "dynamic") {
117+
if (
118+
this.opts.subscriptionMode === "dynamic" ||
119+
this.opts.subscriptionMode === "dynamic-private"
120+
) {
113121
this.rooms.forEach((_sids, room) => {
114-
const isPublicRoom = !this.sids.has(room);
115-
if (isPublicRoom) {
122+
if (this.shouldUseASeparateNamespace(room)) {
116123
channels.push(this.dynamicChannel(room));
117124
}
118125
});
@@ -136,11 +143,13 @@ class ShardedRedisAdapter extends ClusterAdapter {
136143
// broadcast with ack can not use a dynamic channel, because the serverCount() method return the number of all
137144
// servers, not only the ones where the given room exists
138145
const useDynamicChannel =
139-
this.opts.subscriptionMode === "dynamic" &&
140146
message.type === MessageType.BROADCAST &&
141147
message.data.requestId === undefined &&
142148
message.data.opts.rooms.length === 1 &&
143-
!looksLikeASocketId(message.data.opts.rooms[0]);
149+
((this.opts.subscriptionMode === "dynamic" &&
150+
!looksLikeASocketId(message.data.opts.rooms[0])) ||
151+
this.opts.subscriptionMode === "dynamic-private");
152+
144153
if (useDynamicChannel) {
145154
return this.dynamicChannel(message.data.opts.rooms[0]);
146155
} else {
@@ -204,4 +213,13 @@ class ShardedRedisAdapter extends ClusterAdapter {
204213
override serverCount(): Promise<number> {
205214
return PUBSUB(this.pubClient, "SHARDNUMSUB", this.channel);
206215
}
216+
217+
private shouldUseASeparateNamespace(room: string): boolean {
218+
const isPublicRoom = !this.sids.has(room);
219+
220+
return (
221+
(this.opts.subscriptionMode === "dynamic" && isPublicRoom) ||
222+
this.opts.subscriptionMode === "dynamic-private"
223+
);
224+
}
207225
}

Diff for: test/test-runner.ts

+22
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,28 @@ describe("@socket.io/redis-adapter", () => {
175175
true
176176
));
177177

178+
describe("[sharded] redis@4 standalone (dynamic subscription mode & dynamic private channels)", () =>
179+
testSuite(
180+
async () => {
181+
const pubClient = createClient();
182+
const subClient = pubClient.duplicate();
183+
184+
await Promise.all([pubClient.connect(), subClient.connect()]);
185+
186+
return [
187+
createShardedAdapter(pubClient, subClient, {
188+
subscriptionMode: "dynamic-private",
189+
}),
190+
() => {
191+
pubClient.disconnect();
192+
subClient.disconnect();
193+
},
194+
];
195+
},
196+
"redis@4",
197+
true
198+
));
199+
178200
describe("[sharded] redis@4 standalone (static subscription mode)", () =>
179201
testSuite(
180202
async () => {

0 commit comments

Comments
 (0)