Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lib/Command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ export interface CommandNameFlags {
EXIT_SUBSCRIBER_MODE: ["unsubscribe", "punsubscribe", "sunsubscribe"];
// Commands that will make client disconnect from server TODO shutdown?
WILL_DISCONNECT: ["quit"];
// Commands that are sent when the client is connecting
HANDSHAKE_COMMANDS: ["auth", "select", "client", "readonly", "info"];
// Commands that should not trigger a reconnection when errors occur
IGNORE_RECONNECT_ON_ERROR: ["client"];
}

/**
Expand Down Expand Up @@ -95,6 +99,8 @@ export default class Command implements Respondable {
ENTER_SUBSCRIBER_MODE: ["subscribe", "psubscribe", "ssubscribe"],
EXIT_SUBSCRIBER_MODE: ["unsubscribe", "punsubscribe", "sunsubscribe"],
WILL_DISCONNECT: ["quit"],
HANDSHAKE_COMMANDS: ["auth", "select", "client", "readonly", "info"],
IGNORE_RECONNECT_ON_ERROR: ["client"],
};

private static flagMap?: FlagMap;
Expand Down
8 changes: 6 additions & 2 deletions lib/Redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,8 @@
(!stream &&
this.status === "connect" &&
exists(command.name) &&
hasFlag(command.name, "loading"));
(hasFlag(command.name, "loading") ||
Command.checkFlag("HANDSHAKE_COMMANDS", command.name)));
if (!this.stream) {
writable = false;
} else if (!this.stream.writable) {
Expand Down Expand Up @@ -559,35 +560,35 @@
});
}

scanStream(options?: ScanStreamOptions) {

Check warning on line 563 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member scanStream should be declared before all private instance method definitions

Check warning on line 563 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member scanStream should be declared before all private instance method definitions

Check warning on line 563 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member scanStream should be declared before all private instance method definitions

Check warning on line 563 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member scanStream should be declared before all private instance method definitions

Check warning on line 563 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member scanStream should be declared before all private instance method definitions
return this.createScanStream("scan", { options });
}

scanBufferStream(options?: ScanStreamOptions) {

Check warning on line 567 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member scanBufferStream should be declared before all private instance method definitions

Check warning on line 567 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member scanBufferStream should be declared before all private instance method definitions

Check warning on line 567 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member scanBufferStream should be declared before all private instance method definitions

Check warning on line 567 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member scanBufferStream should be declared before all private instance method definitions

Check warning on line 567 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member scanBufferStream should be declared before all private instance method definitions
return this.createScanStream("scanBuffer", { options });
}

sscanStream(key: string, options?: ScanStreamOptions) {

Check warning on line 571 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member sscanStream should be declared before all private instance method definitions

Check warning on line 571 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member sscanStream should be declared before all private instance method definitions

Check warning on line 571 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member sscanStream should be declared before all private instance method definitions

Check warning on line 571 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member sscanStream should be declared before all private instance method definitions

Check warning on line 571 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member sscanStream should be declared before all private instance method definitions
return this.createScanStream("sscan", { key, options });
}

sscanBufferStream(key: string, options?: ScanStreamOptions) {

Check warning on line 575 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member sscanBufferStream should be declared before all private instance method definitions

Check warning on line 575 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member sscanBufferStream should be declared before all private instance method definitions

Check warning on line 575 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member sscanBufferStream should be declared before all private instance method definitions

Check warning on line 575 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member sscanBufferStream should be declared before all private instance method definitions

Check warning on line 575 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member sscanBufferStream should be declared before all private instance method definitions
return this.createScanStream("sscanBuffer", { key, options });
}

hscanStream(key: string, options?: ScanStreamOptions) {

Check warning on line 579 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member hscanStream should be declared before all private instance method definitions

Check warning on line 579 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member hscanStream should be declared before all private instance method definitions

Check warning on line 579 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member hscanStream should be declared before all private instance method definitions

Check warning on line 579 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member hscanStream should be declared before all private instance method definitions

Check warning on line 579 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member hscanStream should be declared before all private instance method definitions
return this.createScanStream("hscan", { key, options });
}

hscanBufferStream(key: string, options?: ScanStreamOptions) {

Check warning on line 583 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member hscanBufferStream should be declared before all private instance method definitions

Check warning on line 583 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member hscanBufferStream should be declared before all private instance method definitions

Check warning on line 583 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member hscanBufferStream should be declared before all private instance method definitions

Check warning on line 583 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member hscanBufferStream should be declared before all private instance method definitions

Check warning on line 583 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member hscanBufferStream should be declared before all private instance method definitions
return this.createScanStream("hscanBuffer", { key, options });
}

zscanStream(key: string, options?: ScanStreamOptions) {

Check warning on line 587 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member zscanStream should be declared before all private instance method definitions

Check warning on line 587 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member zscanStream should be declared before all private instance method definitions

Check warning on line 587 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member zscanStream should be declared before all private instance method definitions

Check warning on line 587 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member zscanStream should be declared before all private instance method definitions

Check warning on line 587 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member zscanStream should be declared before all private instance method definitions
return this.createScanStream("zscan", { key, options });
}

zscanBufferStream(key: string, options?: ScanStreamOptions) {

Check warning on line 591 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member zscanBufferStream should be declared before all private instance method definitions

Check warning on line 591 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member zscanBufferStream should be declared before all private instance method definitions

Check warning on line 591 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member zscanBufferStream should be declared before all private instance method definitions

Check warning on line 591 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member zscanBufferStream should be declared before all private instance method definitions

Check warning on line 591 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member zscanBufferStream should be declared before all private instance method definitions
return this.createScanStream("zscanBuffer", { key, options });
}

Expand All @@ -596,7 +597,7 @@
*
* @ignore
*/
silentEmit(eventName: string, arg?: unknown): boolean {

Check warning on line 600 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member silentEmit should be declared before all private instance method definitions

Check warning on line 600 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member silentEmit should be declared before all private instance method definitions

Check warning on line 600 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member silentEmit should be declared before all private instance method definitions

Check warning on line 600 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member silentEmit should be declared before all private instance method definitions

Check warning on line 600 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member silentEmit should be declared before all private instance method definitions
let error: unknown;
if (eventName === "error") {
error = arg;
Expand Down Expand Up @@ -631,7 +632,7 @@
/**
* @ignore
*/
recoverFromFatalError(

Check warning on line 635 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (20.x)

Member recoverFromFatalError should be declared before all private instance method definitions

Check warning on line 635 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (14.x)

Member recoverFromFatalError should be declared before all private instance method definitions

Check warning on line 635 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (16.x)

Member recoverFromFatalError should be declared before all private instance method definitions

Check warning on line 635 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (18.x)

Member recoverFromFatalError should be declared before all private instance method definitions

Check warning on line 635 in lib/Redis.ts

View workflow job for this annotation

GitHub Actions / test / test (12.x)

Member recoverFromFatalError should be declared before all private instance method definitions
_commandError: Error,
err: Error,
options: FlushQueueOptions
Expand All @@ -646,7 +647,10 @@
*/
handleReconnection(err: Error, item: CommandItem) {
let needReconnect: ReturnType<ReconnectOnError> = false;
if (this.options.reconnectOnError) {
if (
this.options.reconnectOnError &&
!Command.checkFlag("IGNORE_RECONNECT_ON_ERROR", item.command.name)
) {
needReconnect = this.options.reconnectOnError(err);
}

Expand Down
1 change: 1 addition & 0 deletions lib/autoPipelining.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const notAllowedAutoPipelineCommands = [
"unsubscribe",
"unpsubscribe",
"select",
"client",
];

function executeAutoPipeline(client, slotKey: string) {
Expand Down
114 changes: 60 additions & 54 deletions lib/redis/event_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,6 @@ export function connectHandler(self) {
});
}

if (!self.options.enableReadyCheck) {
exports.readyHandler(self)();
}

/*
No need to keep the reference of DataHandler here
because we don't need to do the cleanup.
Expand All @@ -79,27 +75,69 @@ export function connectHandler(self) {
stringNumbers: self.options.stringNumbers,
});

if (self.options.enableReadyCheck) {
self._readyCheck(function (err, info) {
if (connectionEpoch !== self.connectionEpoch) {
return;
const clientCommandPromises = [];

if (self.options.connectionName) {
debug("set the connection name [%s]", self.options.connectionName);
clientCommandPromises.push(
self.client("setname", self.options.connectionName).catch(noop)
);
}

if (!self.options.disableClientInfo) {
debug("set the client info");
clientCommandPromises.push(
getPackageMeta()
.then((packageMeta) => {
return self
.client("SETINFO", "LIB-VER", packageMeta.version)
.catch(noop);
})
.catch(noop)
);

clientCommandPromises.push(
self
.client(
"SETINFO",
"LIB-NAME",
self.options?.clientInfoTag
? `ioredis(${self.options.clientInfoTag})`
: "ioredis"
)
.catch(noop)
);
}

Promise.all(clientCommandPromises)
.catch(noop)
.finally(() => {
if (!self.options.enableReadyCheck) {
exports.readyHandler(self)();
}
if (err) {
if (!flushed) {
self.recoverFromFatalError(
new Error("Ready check failed: " + err.message),
err
);
}
} else {
if (self.connector.check(info)) {
exports.readyHandler(self)();
} else {
self.disconnect(true);
}

if (self.options.enableReadyCheck) {
self._readyCheck(function (err, info) {
if (connectionEpoch !== self.connectionEpoch) {
return;
}
if (err) {
if (!flushed) {
self.recoverFromFatalError(
new Error("Ready check failed: " + err.message),
err
);
}
} else {
if (self.connector.check(info)) {
exports.readyHandler(self)();
} else {
self.disconnect(true);
}
}
});
}
});
}
};
}

Expand Down Expand Up @@ -264,38 +302,6 @@ export function readyHandler(self) {
? self.prevCondition.select
: self.condition.select;

if (self.options.connectionName) {
debug("set the connection name [%s]", self.options.connectionName);
self.client("setname", self.options.connectionName).catch(noop);
}

if (!self.options?.disableClientInfo) {
debug("set the client info");

let version = null;

getPackageMeta()
.then((packageMeta) => {
version = packageMeta?.version;
})
.catch(noop)
.finally(() => {
self
.client("SETINFO", "LIB-VER", version)
.catch(noop);
});

self
.client(
"SETINFO",
"LIB-NAME",
self.options?.clientInfoTag
? `ioredis(${self.options.clientInfoTag})`
: "ioredis"
)
.catch(noop);
}

if (self.options.readOnly) {
debug("set the connection to readonly mode");
self.readonly().catch(noop);
Expand Down
11 changes: 11 additions & 0 deletions test/cluster/basic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,17 @@ describe("cluster", () => {
cluster.set("foo", "auto pipelining");
expect(await cluster.get("foo")).to.eql("auto pipelining");
});

it("client setinfo works with auto pipelining", async () => {
const cluster = new Cluster([{ host: "127.0.0.1", port: masters[0] }], {
enableAutoPipelining: true,
});

for (let i = 0; i < 100; i++) {
await cluster.set(`foo${i}`, "bar");
expect(await cluster.get(`foo${i}`)).to.eql("bar");
}
});
});

describe("key prefixing", () => {
Expand Down
10 changes: 5 additions & 5 deletions test/functional/client_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ describe("clientInfo", function () {
it("should send client info by default", async () => {
redis = new Redis({ port: 30001 });

// Wait for the client info to be sent, as it happens after the ready event
// Wait for the client info to be sent, to make sure the connection is established
await redis.ping();

expect(clientInfoCommands).to.have.length(2);
Expand All @@ -56,7 +56,7 @@ describe("clientInfo", function () {
it("should not send client info when disableClientInfo is true", async () => {
redis = new Redis({ port: 30001, disableClientInfo: true });

// Wait for the client info to be sent, as it happens after the ready event
// Wait for the client info to be sent, to make sure the commands are sent
await redis.ping();

expect(clientInfoCommands).to.have.length(0);
Expand All @@ -65,7 +65,7 @@ describe("clientInfo", function () {
it("should append tag to library name when clientInfoTag is set", async () => {
redis = new Redis({ port: 30001, clientInfoTag: "tag-test" });

// Wait for the client info to be sent, as it happens after the ready event
// Wait for the client info to be sent, to make sure the commands are sent
await redis.ping();

expect(clientInfoCommands).to.have.length(2);
Expand All @@ -80,7 +80,7 @@ describe("clientInfo", function () {
it("should send client info after reconnection", async () => {
redis = new Redis({ port: 30001 });

// Wait for the client info to be sent, as it happens after the ready event
// Wait for the client info to be sent, to make sure the commands are sent
await redis.ping();
redis.disconnect();

Expand Down Expand Up @@ -171,7 +171,7 @@ describe("clientInfo", function () {
it("should send client info by default", async () => {
cluster = new Redis.Cluster([{ host: "127.0.0.1", port: 30001 }]);

// Wait for cluster to be ready and send a command to ensure connection
// Wait for the client info to be sent, to make sure the commands are sent
await cluster.ping();

// Should have sent 2 SETINFO commands (LIB-VER and LIB-NAME)
Expand Down
1 change: 1 addition & 0 deletions test/functional/cluster/autopipelining.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ describe("autoPipelining for cluster", () => {

promises.push(cluster.subscribe("subscribe").catch(() => {}));
promises.push(cluster.unsubscribe("subscribe").catch(() => {}));
promises.push(cluster.client("help").catch(() => {}));

expect(cluster.autoPipelineQueueSize).to.eql(0);
await promises;
Expand Down
6 changes: 4 additions & 2 deletions test/functional/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ describe("connection", function () {
times += 1;
if (times === 1) {
expect(command.name).to.eql("auth");
} else if (times === 2) {
} else if (times === 2 || times === 3) {
expect(command.name).to.eql("client");
} else if (times === 4) {
expect(command.name).to.eql("info");
} else if (times === 3) {
} else if (times === 5) {
redis.disconnect();
setImmediate(() => done());
}
Expand Down
16 changes: 16 additions & 0 deletions test/functional/reconnect_on_error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,20 @@ describe("reconnectOnError", () => {
done();
});
});

it("should not reconnect if reconnectOnError if ignored command fails", (done) => {
const redis = new Redis({
reconnectOnError: function (err) {
return true;
},
});

redis.disconnect = () => {
throw new Error("should not disconnect");
};

redis.client("some-wrong-arg", function (err) {
done();
});
});
});