Skip to content

Commit 16a0610

Browse files
authored
fix: handle connection after connect event was emitted (#1095)
It's possible for connect promise to resolve after the resolved connection has already been established, which means we miss the connect event. This can happen when Bluebird is set as the global Promise, and we connect to an ip address host for example. This fix checks to see if that's the case and invokes the connection handler directly. Thanks @luin for the recommendation. Fixes #977
1 parent e22cae6 commit 16a0610

File tree

2 files changed

+63
-32
lines changed

2 files changed

+63
-32
lines changed

Diff for: lib/redis/index.ts

+36-32
Original file line numberDiff line numberDiff line change
@@ -304,41 +304,45 @@ Redis.prototype.connect = function (callback) {
304304
stream.setKeepAlive(true, options.keepAlive);
305305
}
306306

307-
stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this));
307+
if (stream.connecting) {
308+
stream.once(CONNECT_EVENT, eventHandler.connectHandler(_this));
309+
310+
if (options.connectTimeout) {
311+
/*
312+
* Typically, Socket#setTimeout(0) will clear the timer
313+
* set before. However, in some platforms (Electron 3.x~4.x),
314+
* the timer will not be cleared. So we introduce a variable here.
315+
*
316+
* See https://github.com/electron/electron/issues/14915
317+
*/
318+
let connectTimeoutCleared = false;
319+
stream.setTimeout(options.connectTimeout, function () {
320+
if (connectTimeoutCleared) {
321+
return;
322+
}
323+
stream.setTimeout(0);
324+
stream.destroy();
325+
326+
const err = new Error("connect ETIMEDOUT");
327+
// @ts-ignore
328+
err.errorno = "ETIMEDOUT";
329+
// @ts-ignore
330+
err.code = "ETIMEDOUT";
331+
// @ts-ignore
332+
err.syscall = "connect";
333+
eventHandler.errorHandler(_this)(err);
334+
});
335+
stream.once(CONNECT_EVENT, function () {
336+
connectTimeoutCleared = true;
337+
stream.setTimeout(0);
338+
});
339+
}
340+
} else {
341+
process.nextTick(eventHandler.connectHandler(_this));
342+
}
308343
stream.once("error", eventHandler.errorHandler(_this));
309344
stream.once("close", eventHandler.closeHandler(_this));
310345

311-
if (options.connectTimeout) {
312-
/*
313-
* Typically, Socket#setTimeout(0) will clear the timer
314-
* set before. However, in some platforms (Electron 3.x~4.x),
315-
* the timer will not be cleared. So we introduce a variable here.
316-
*
317-
* See https://github.com/electron/electron/issues/14915
318-
*/
319-
let connectTimeoutCleared = false;
320-
stream.setTimeout(options.connectTimeout, function () {
321-
if (connectTimeoutCleared) {
322-
return;
323-
}
324-
stream.setTimeout(0);
325-
stream.destroy();
326-
327-
const err = new Error("connect ETIMEDOUT");
328-
// @ts-ignore
329-
err.errorno = "ETIMEDOUT";
330-
// @ts-ignore
331-
err.code = "ETIMEDOUT";
332-
// @ts-ignore
333-
err.syscall = "connect";
334-
eventHandler.errorHandler(_this)(err);
335-
});
336-
stream.once(CONNECT_EVENT, function () {
337-
connectTimeoutCleared = true;
338-
stream.setTimeout(0);
339-
});
340-
}
341-
342346
if (options.noDelay) {
343347
stream.setNoDelay(true);
344348
}

Diff for: test/functional/connection.ts

+27
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as sinon from "sinon";
44
import { expect } from "chai";
55
import MockServer from "../helpers/mock_server";
66
import * as Bluebird from "bluebird";
7+
import { StandaloneConnector } from "../../lib/connectors";
78

89
describe("connection", function () {
910
it('should emit "connect" when connected', function (done) {
@@ -423,5 +424,31 @@ describe("connection", function () {
423424
done();
424425
});
425426
});
427+
428+
it("works when connection established before promise is resolved", (done) => {
429+
const socket = new net.Socket();
430+
sinon.stub(StandaloneConnector.prototype, "connect").resolves(socket);
431+
socket.connect(6379, "127.0.0.1").on("connect", () => {
432+
new Redis().on("connect", () => done());
433+
});
434+
});
435+
436+
it("ignores connectTimeout when connection established before promise is resolved", (done) => {
437+
const socketSetTimeoutSpy = sinon.spy(net.Socket.prototype, "setTimeout");
438+
const socket = new net.Socket();
439+
sinon.stub(StandaloneConnector.prototype, "connect").resolves(socket);
440+
socket.connect(6379, "127.0.0.1").on("connect", () => {
441+
const redis = new Redis({
442+
connectTimeout: 1,
443+
});
444+
redis.on("error", () =>
445+
done(new Error("Connect timeout should not have been called"))
446+
);
447+
redis.on("connect", () => {
448+
expect(socketSetTimeoutSpy.callCount).to.eql(0);
449+
done();
450+
});
451+
});
452+
});
426453
});
427454
});

0 commit comments

Comments
 (0)