You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-17 19:41:06 +03:00
better cluster nodes discorvery strategy after MOVED error, add PubSub test
This commit is contained in:
@@ -4,6 +4,7 @@ import { itWithClient, TEST_REDIS_SERVERS, TestRedisServers } from './test-utils
|
||||
import RedisClient from './client';
|
||||
import { AbortError } from './errors';
|
||||
import { defineScript } from './lua-script';
|
||||
import { spy } from 'sinon';
|
||||
|
||||
describe('Client', () => {
|
||||
describe('authentication', () => {
|
||||
@@ -342,4 +343,50 @@ describe('Client', () => {
|
||||
keys.sort()
|
||||
);
|
||||
});
|
||||
|
||||
itWithClient(TestRedisServers.OPEN, 'PubSub', async publisher => {
|
||||
const subscriber = publisher.duplicate();
|
||||
|
||||
await subscriber.connect();
|
||||
|
||||
try {
|
||||
const channelListener1 = spy(),
|
||||
channelListener2 = spy(),
|
||||
patternListener = spy();
|
||||
|
||||
await Promise.all([
|
||||
subscriber.subscribe('channel', channelListener1),
|
||||
subscriber.subscribe('channel', channelListener2),
|
||||
subscriber.pSubscribe('channel*', patternListener)
|
||||
]);
|
||||
|
||||
await publisher.publish('channel', 'message');
|
||||
assert.ok(channelListener1.calledOnceWithExactly('message', 'channel'));
|
||||
assert.ok(channelListener2.calledOnceWithExactly('message', 'channel'));
|
||||
assert.ok(patternListener.calledOnceWithExactly('message', 'channel'));
|
||||
|
||||
await subscriber.unsubscribe('channel', channelListener1);
|
||||
await publisher.publish('channel', 'message');
|
||||
assert.ok(channelListener1.calledOnce);
|
||||
assert.ok(channelListener2.calledTwice);
|
||||
assert.ok(channelListener2.secondCall.calledWithExactly('message', 'channel'));
|
||||
assert.ok(patternListener.calledTwice);
|
||||
assert.ok(patternListener.secondCall.calledWithExactly('message', 'channel'));
|
||||
|
||||
await subscriber.unsubscribe('channel');
|
||||
await publisher.publish('channel', 'message');
|
||||
assert.ok(channelListener1.calledOnce);
|
||||
assert.ok(channelListener2.calledTwice);
|
||||
assert.ok(patternListener.calledThrice);
|
||||
assert.ok(patternListener.thirdCall.calledWithExactly('message', 'channel'));
|
||||
|
||||
await subscriber.pUnsubscribe();
|
||||
await publisher.publish('channel', 'message');
|
||||
assert.ok(channelListener1.calledOnce);
|
||||
assert.ok(channelListener2.calledTwice);
|
||||
assert.ok(patternListener.calledThrice);
|
||||
} finally {
|
||||
await subscriber.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
@@ -306,19 +306,19 @@ export default class RedisClient<M extends RedisModules = RedisModules, S extend
|
||||
return promise;
|
||||
}
|
||||
|
||||
UNSUBSCRIBE(channels: string | Array<string>, listener?: PubSubListener): Promise<void> {
|
||||
UNSUBSCRIBE(channels?: string | Array<string>, listener?: PubSubListener): Promise<void> {
|
||||
return this.#unsubscribe(PubSubUnsubscribeCommands.UNSUBSCRIBE, channels, listener);
|
||||
}
|
||||
|
||||
unsubscribe = this.UNSUBSCRIBE;
|
||||
|
||||
PUNSUBSCRIBE(patterns: string | Array<string>, listener?: PubSubListener): Promise<void> {
|
||||
PUNSUBSCRIBE(patterns?: string | Array<string>, listener?: PubSubListener): Promise<void> {
|
||||
return this.#unsubscribe(PubSubUnsubscribeCommands.PUNSUBSCRIBE, patterns, listener);
|
||||
}
|
||||
|
||||
pUnsubscribe = this.PUNSUBSCRIBE;
|
||||
|
||||
#unsubscribe(command: PubSubUnsubscribeCommands, channels: string | Array<string>, listener?: PubSubListener): Promise<void> {
|
||||
#unsubscribe(command: PubSubUnsubscribeCommands, channels?: string | Array<string>, listener?: PubSubListener): Promise<void> {
|
||||
const promise = this.#queue.unsubscribe(command, channels, listener);
|
||||
this.#tick();
|
||||
return promise;
|
||||
|
@@ -32,9 +32,17 @@ export default class RedisClusterSlots {
|
||||
throw new Error('None of the root nodes is available');
|
||||
}
|
||||
|
||||
async discover(): Promise<void> {
|
||||
// TODO: shuffle?
|
||||
async discover(startWith: RedisClient): Promise<void> {
|
||||
try {
|
||||
await this.#discoverNodes(startWith.options?.socket);
|
||||
return;
|
||||
} catch (err) {
|
||||
// this.emit('error', err);
|
||||
}
|
||||
|
||||
for (const client of this.#clientByKey.values()) {
|
||||
if (client === startWith) continue;
|
||||
|
||||
try {
|
||||
await this.#discoverNodes(client.options?.socket);
|
||||
return;
|
||||
|
@@ -61,7 +61,7 @@ export default class RedisCluster {
|
||||
if (err.message.startsWith('ASK')) {
|
||||
// TODO
|
||||
} else if (err.message.startsWith('MOVED')) {
|
||||
await this.#slots.discover();
|
||||
await this.#slots.discover(client);
|
||||
|
||||
if (redirections < (this.#options.maxCommandRedirections ?? 16)) {
|
||||
return this.sendCommand(args, firstKeyIndex, isReadonly, redirections + 1);
|
||||
|
@@ -204,9 +204,15 @@ export default class RedisCommandsQueue {
|
||||
return this.#pushPubSubCommand(command, channelsToSubscribe);
|
||||
}
|
||||
|
||||
unsubscribe(command: PubSubUnsubscribeCommands, channels: string | Array<string>, listener?: PubSubListener) {
|
||||
const listeners = command === PubSubUnsubscribeCommands.UNSUBSCRIBE ? this.#pubSubListeners.channels : this.#pubSubListeners.patterns,
|
||||
channelsToUnsubscribe = [];
|
||||
unsubscribe(command: PubSubUnsubscribeCommands, channels?: string | Array<string>, listener?: PubSubListener) {
|
||||
const listeners = command === PubSubUnsubscribeCommands.UNSUBSCRIBE ? this.#pubSubListeners.channels : this.#pubSubListeners.patterns;
|
||||
if (!channels) {
|
||||
const keys = [...listeners.keys()];
|
||||
listeners.clear();
|
||||
return this.#pushPubSubCommand(command, keys);
|
||||
}
|
||||
|
||||
const channelsToUnsubscribe = [];
|
||||
for (const channel of (Array.isArray(channels) ? channels : [channels])) {
|
||||
const set = listeners.get(channel);
|
||||
if (!set) continue;
|
||||
|
Reference in New Issue
Block a user