From 2f9928ba8f6f91a2f977f8dc1d31cf9be48896de Mon Sep 17 00:00:00 2001 From: luin Date: Sun, 14 Mar 2021 23:22:26 +0800 Subject: [PATCH] Handle instant stream error --- lib/connectors/AbstractConnector.ts | 1 + lib/connectors/SentinelConnector/index.ts | 5 ++++ lib/connectors/StandaloneConnector.ts | 4 +++ lib/redis/index.ts | 16 +++++++++-- test/functional/connection.ts | 35 +++++++++++++++++++++++ test/unit/connectors/connector.ts | 4 +-- 6 files changed, 60 insertions(+), 5 deletions(-) diff --git a/lib/connectors/AbstractConnector.ts b/lib/connectors/AbstractConnector.ts index 1d9b2022..276c3313 100644 --- a/lib/connectors/AbstractConnector.ts +++ b/lib/connectors/AbstractConnector.ts @@ -5,6 +5,7 @@ export type ErrorEmitter = (type: string, err: Error) => void; export default abstract class AbstractConnector { protected connecting = false; protected stream: NetStream; + public firstError?: Error; public check(info: any): boolean { return true; diff --git a/lib/connectors/SentinelConnector/index.ts b/lib/connectors/SentinelConnector/index.ts index bcde400d..4349677d 100644 --- a/lib/connectors/SentinelConnector/index.ts +++ b/lib/connectors/SentinelConnector/index.ts @@ -135,6 +135,11 @@ export default class SentinelConnector extends AbstractConnector { } else { this.stream = createConnection(resolved); } + + this.stream.once("error", (err) => { + this.firstError = err; + }); + this.sentinelIterator.reset(true); resolve(this.stream); } else { diff --git a/lib/connectors/StandaloneConnector.ts b/lib/connectors/StandaloneConnector.ts index 71a36fdd..39a3613d 100644 --- a/lib/connectors/StandaloneConnector.ts +++ b/lib/connectors/StandaloneConnector.ts @@ -76,6 +76,10 @@ export default class StandaloneConnector extends AbstractConnector { return; } + this.stream.once("error", (err) => { + this.firstError = err; + }); + resolve(this.stream); }); }); diff --git a/lib/redis/index.ts b/lib/redis/index.ts index 7c6b255e..54adfa75 100644 --- a/lib/redis/index.ts +++ b/lib/redis/index.ts @@ -287,7 +287,7 @@ Redis.prototype.setStatus = function (status, arg) { */ Redis.prototype.connect = function (callback) { const _Promise = PromiseContainer.get(); - const promise = new _Promise((resolve, reject) => { + const promise = new _Promise((resolve, reject) => { if ( this.status === "connecting" || this.status === "connect" || @@ -370,11 +370,21 @@ Redis.prototype.connect = function (callback) { stream.setTimeout(0); }); } + } else if (stream.destroyed) { + const firstError = _this.connector.firstError; + if (firstError) { + process.nextTick(() => { + eventHandler.errorHandler(_this)(firstError); + }); + } + process.nextTick(eventHandler.closeHandler(_this)); } else { process.nextTick(eventHandler.connectHandler(_this)); } - stream.once("error", eventHandler.errorHandler(_this)); - stream.once("close", eventHandler.closeHandler(_this)); + if (!stream.destroyed) { + stream.once("error", eventHandler.errorHandler(_this)); + stream.once("close", eventHandler.closeHandler(_this)); + } if (options.noDelay) { stream.setNoDelay(true); diff --git a/test/functional/connection.ts b/test/functional/connection.ts index 90064b29..a527054c 100644 --- a/test/functional/connection.ts +++ b/test/functional/connection.ts @@ -5,6 +5,7 @@ import { expect } from "chai"; import MockServer from "../helpers/mock_server"; import * as Bluebird from "bluebird"; import { StandaloneConnector } from "../../lib/connectors"; +import { CONNECTION_CLOSED_ERROR_MSG } from "../../lib/utils"; describe("connection", function () { it('should emit "connect" when connected', function (done) { @@ -240,6 +241,40 @@ describe("connection", function () { done(); }); }); + + it("should close if socket destroyed before being returned", function (done) { + const message = "instant error"; + sinon.stub(net, "createConnection").callsFake(function () { + const socket = (net.createConnection as any).wrappedMethod.apply( + net, + arguments + ) as net.Socket; + socket.destroy(new Error(message)); + return socket; + }); + + let closed = false; + let errored = false; + + const redis = new Redis({ lazyConnect: true }); + redis + .connect(() => {}) + .catch((err) => { + expect(closed).to.equal(true); + expect(err.message).to.eql(CONNECTION_CLOSED_ERROR_MSG); + redis.disconnect(); + done(); + }); + + redis.on("error", (err) => { + expect(err.message).to.equal(message); + errored = true; + }); + redis.on("close", () => { + expect(errored).to.equal(true); + closed = true; + }); + }); }); describe("retryStrategy", function () { diff --git a/test/unit/connectors/connector.ts b/test/unit/connectors/connector.ts index 45a19731..e7939ac1 100644 --- a/test/unit/connectors/connector.ts +++ b/test/unit/connectors/connector.ts @@ -20,7 +20,7 @@ describe("StandaloneConnector", () => { }); it("ignore path when port is set and path is null", async () => { - const spy = sinon.stub(net, "createConnection"); + const spy = sinon.spy(net, "createConnection"); const connector = new StandaloneConnector({ port: 6379, path: null }); await connector.connect(() => {}); expect(spy.calledOnce).to.eql(true); @@ -29,7 +29,7 @@ describe("StandaloneConnector", () => { }); it("supports tls", async () => { - const spy = sinon.stub(tls, "connect"); + const spy = sinon.spy(tls, "connect"); const connector = new StandaloneConnector({ port: 6379, tls: { ca: "on" },