1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-07 13:22:56 +03:00

fix ASK and MOVED errors in multi as well

This commit is contained in:
leibale
2021-12-30 17:12:13 -05:00
parent 29ff6c8a36
commit 8f88eb289b

View File

@@ -1,5 +1,5 @@
import COMMANDS from './commands'; import COMMANDS from './commands';
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands'; import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
import { ClientCommandOptions, RedisClientCommandSignature, RedisClientOptions, RedisClientType, WithModules, WithScripts } from '../client'; import { ClientCommandOptions, RedisClientCommandSignature, RedisClientOptions, RedisClientType, WithModules, WithScripts } from '../client';
import RedisClusterSlots, { ClusterNode } from './cluster-slots'; import RedisClusterSlots, { ClusterNode } from './cluster-slots';
import { extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, extendWithCommands } from '../commander'; import { extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, extendWithCommands } from '../commander';
@@ -82,27 +82,17 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
); );
} }
async sendCommand<C extends RedisCommand>( async sendCommand<T = RedisCommandRawReply>(
firstKey: RedisCommandArgument | undefined, firstKey: RedisCommandArgument | undefined,
isReadonly: boolean | undefined, isReadonly: boolean | undefined,
args: RedisCommandArguments, args: RedisCommandArguments,
options?: ClientCommandOptions, options?: ClientCommandOptions
redirections = 0 ): Promise<T> {
): Promise<RedisCommandReply<C>> { return this.#execute(
const client = this.#slots.getClient(firstKey, isReadonly); firstKey,
isReadonly,
try { client => client.sendCommand<T>(args, options)
return await client.sendCommand(args, options); );
} catch (err: any) {
const shouldRetry = await this.#handleCommandError(err, client, redirections);
if (shouldRetry === true) {
return this.sendCommand(firstKey, isReadonly, args, options, redirections + 1);
} else if (shouldRetry) {
return shouldRetry.sendCommand(args, options);
}
throw err;
}
} }
async scriptsExecutor(script: RedisScript, args: Array<unknown>): Promise<RedisCommandReply<typeof script>> { async scriptsExecutor(script: RedisScript, args: Array<unknown>): Promise<RedisCommandReply<typeof script>> {
@@ -124,61 +114,65 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
script: RedisScript, script: RedisScript,
originalArgs: Array<unknown>, originalArgs: Array<unknown>,
redisArgs: RedisCommandArguments, redisArgs: RedisCommandArguments,
options?: ClientCommandOptions, options?: ClientCommandOptions
redirections = 0
): Promise<RedisCommandReply<typeof script>> { ): Promise<RedisCommandReply<typeof script>> {
const client = this.#slots.getClient( return this.#execute(
RedisCluster.extractFirstKey(script, originalArgs, redisArgs), RedisCluster.extractFirstKey(script, originalArgs, redisArgs),
script.IS_READ_ONLY script.IS_READ_ONLY,
client => client.executeScript(script, redisArgs, options)
); );
try {
return await client.executeScript(script, redisArgs, options);
} catch (err: any) {
const shouldRetry = await this.#handleCommandError(err, client, redirections);
if (shouldRetry === true) {
return this.executeScript(script, originalArgs, redisArgs, options, redirections + 1);
} else if (shouldRetry) {
return shouldRetry.executeScript(script, redisArgs, options);
}
throw err;
}
} }
async #handleCommandError(err: Error, client: RedisClientType<M, S>, redirections: number): Promise<boolean | RedisClientType<M, S>> { async #execute<Reply>(
if (redirections > (this.#options.maxCommandRedirections ?? 16)) { firstKey: RedisCommandArgument | undefined,
throw err; isReadonly: boolean | undefined,
} executor: (client: RedisClientType<M, S>) => Promise<Reply>
): Promise<Reply> {
if (err.message.startsWith('ASK')) { const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16;
const url = err.message.substring(err.message.lastIndexOf(' ') + 1); let client = this.#slots.getClient(firstKey, isReadonly);
let node = this.#slots.getNodeByUrl(url); for (let i = 0;; i++) {
if (!node) { try {
await this.#slots.rediscover(client); return await executor(client);
node = this.#slots.getNodeByUrl(url); } catch (err) {
if (++i > maxCommandRedirections || !(err instanceof Error)) {
if (!node) { throw err;
throw new Error(`Cannot find node ${url}`);
} }
if (err.message.startsWith('ASK')) {
const url = err.message.substring(err.message.lastIndexOf(' ') + 1);
if (this.#slots.getNodeByUrl(url)?.client === client) {
await client.asking();
continue;
}
await this.#slots.rediscover(client);
const redirectTo = this.#slots.getNodeByUrl(url);
if (!redirectTo) {
throw new Error(`Cannot find node ${url}`);
}
await redirectTo.client.asking();
client = redirectTo.client;
continue;
} else if (err.message.startsWith('MOVED')) {
await this.#slots.rediscover(client);
client = this.#slots.getClient(firstKey, isReadonly);
continue;
}
throw err;
} }
await node.client.asking();
return node.client;
} else if (err.message.startsWith('MOVED')) {
await this.#slots.rediscover(client);
return true;
} }
throw err;
} }
multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, S> { multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, S> {
return new this.#Multi( return new this.#Multi(
async (commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => { (commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => {
return this.#slots return this.#execute(
.getClient(firstKey) firstKey,
.multiExecutor(commands, chainId); false,
client => client.multiExecutor(commands, chainId)
);
}, },
routing routing
); );