Skip to content

Commit

Permalink
feat: 馃幐 handle -ASK redirects
Browse files Browse the repository at this point in the history
  • Loading branch information
streamich committed Dec 16, 2023
1 parent 25806a8 commit cb5056f
Show file tree
Hide file tree
Showing 3 changed files with 46 additions and 7 deletions.
8 changes: 6 additions & 2 deletions src/cluster/RedisCluster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {PartialExcept, RedisClientCodecOpts} from '../types';
import {RedisClusterRouter} from './RedisClusterRouter';
import {RedisClusterNode} from './RedisClusterNode';
import {RedisClusterCall} from './RedisClusterCall';
import {isMovedError, parseMovedError} from './errors';
import {isAskError, isMovedError, parseMovedError} from './errors';
import {RedisClusterNodeClient, RedisClusterNodeClientOpts} from './RedisClusterNodeClient';
import {RedirectType} from './constants';
import {withTimeout} from '../util/timeout';
Expand Down Expand Up @@ -270,8 +270,12 @@ export class RedisCluster implements Printable {
const nextClient = await this.getRedirectClient(host, port, client);
const next = RedisClusterCall.redirect(call, nextClient, RedirectType.MOVED);
return await this.__call(next);
} else if (isAskError(error)) {
const next = RedisClusterCall.retry(call, client);
const delay = 50 * 2 ** (next.retry - 1);
await new Promise((resolve) => setTimeout(resolve, delay));
return await this.__call(next);
}
// TODO: Handle ASK redirection.
throw error;
}
}
Expand Down
35 changes: 30 additions & 5 deletions src/cluster/RedisClusterCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,35 @@ import type {RedisClusterNodeClient} from "./RedisClusterNodeClient";
* Represents a single Redis request/response command call.
*/
export class RedisClusterCall extends RedisCall {
public static redirect(call: RedisClusterCall, client: RedisClusterNodeClient, type: RedirectType): RedisClusterCall {
public static chain(call: RedisClusterCall, client: RedisClusterNodeClient): RedisClusterCall {
const next = new RedisClusterCall(call.args);
// next.prev = call;
next.client = client;
next.redirect = type;
next.redirects = call.redirects + 1;
next.redirects = call.redirects;
next.maxRedirects = call.maxRedirects;
if (next.redirects > next.maxRedirects) throw new Error('MAX_REDIRECTS');
next.retry = call.retry;
next.maxRetries = call.maxRetries;
next.utf8Res = call.utf8Res;
next.noRes = call.noRes;
next.key = call.key;
return next;
}

public static redirect(call: RedisClusterCall, client: RedisClusterNodeClient, type: RedirectType): RedisClusterCall {
const next = RedisClusterCall.chain(call, client);
// next.redirect = type;
next.redirects = call.redirects + 1;
if (next.redirects > next.maxRedirects) throw new Error('MAX_REDIRECTS');
return next;
}

public static retry(call: RedisClusterCall, client: RedisClusterNodeClient): RedisClusterCall {
const next = RedisClusterCall.chain(call, client);
next.retry = call.retry + 1;
if (next.retry > next.maxRetries) throw new Error('MAX_RETRIES');
return next;
}

/**
* Key to use for routing the command to the correct node.
*/
Expand All @@ -28,7 +43,7 @@ export class RedisClusterCall extends RedisCall {
/**
* Type of redirect of this call.
*/
public redirect: RedirectType = RedirectType.NONE;
// public redirect: RedirectType = RedirectType.NONE;

/**
* Number of redirects that have been performed for this command.
Expand All @@ -40,6 +55,16 @@ export class RedisClusterCall extends RedisCall {
*/
public maxRedirects: number = 4;

/**
* Number of times this command has been retried.
*/
public retry: number = 0;

/**
* Maximum number of retries to perform before giving up.
*/
public maxRetries: number = 4;

/**
* Client used for this call.
*/
Expand Down
10 changes: 10 additions & 0 deletions src/cluster/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@ export const isMovedError = (error: unknown): boolean => {
return false;
};

export const isAskError = (error: unknown): boolean => {
if (error instanceof Error) {
const msg = error.message;
return msg.charCodeAt(0) === 65 && // A
msg.charCodeAt(1) === 83 && // S
msg.charCodeAt(2) === 75; // K
}
return false;
};

const movedErrorRegex = /^MOVED [0-9]+ ([^:]*):([0-9]+)\s*$/;
export const parseMovedError = (message: string): [endpoint: string, port: number] => {
const match = movedErrorRegex.exec(message);
Expand Down

0 comments on commit cb5056f

Please sign in to comment.