diff --git a/lib/cluster/connection_pool.js b/lib/cluster/connection_pool.js index 620b0998..0c385860 100644 --- a/lib/cluster/connection_pool.js +++ b/lib/cluster/connection_pool.js @@ -74,6 +74,10 @@ ConnectionPool.prototype.findOrCreate = function (node, readOnly) { }); this.emit('+node', redis); + + redis.on('error', function (error) { + _this.emit('nodeError', error); + }); } return redis; diff --git a/lib/cluster/index.js b/lib/cluster/index.js index 76ce4d00..e1bd887f 100644 --- a/lib/cluster/index.js +++ b/lib/cluster/index.js @@ -68,6 +68,9 @@ function Cluster(startupNodes, options) { this.connectionPool.on('drain', function () { _this.setStatus('close'); }); + this.connectionPool.on('nodeError', function (error) { + _this.emit('node error', error); + }); this.slots = []; this.retryAttempts = 0; @@ -364,7 +367,7 @@ Cluster.prototype.executeOfflineCommands = function () { Cluster.prototype.sendCommand = function (command, stream, node) { if (this.status === 'end') { - command.reject(new Error('Connection is closed.')); + command.reject(new Error(utils.CONNECTION_CLOSED_ERROR_MSG)); return command.promise; } var to = this.options.scaleReads; @@ -506,7 +509,7 @@ Cluster.prototype.handleError = function (error, ttl, handlers) { timeout: this.options.retryDelayOnClusterDown, callback: this.refreshSlotsCache.bind(this) }); - } else if (error.message === 'Connection is closed.' && this.options.retryDelayOnFailover > 0) { + } else if (error.message === utils.CONNECTION_CLOSED_ERROR_MSG && this.options.retryDelayOnFailover > 0) { this.delayQueue.push('failover', handlers.connectionClosed, { timeout: this.options.retryDelayOnFailover, callback: this.refreshSlotsCache.bind(this) diff --git a/lib/connectors/connector.js b/lib/connectors/connector.js index 469e9be2..1b7f9892 100644 --- a/lib/connectors/connector.js +++ b/lib/connectors/connector.js @@ -3,6 +3,7 @@ var _ = require('lodash'); var net = require('net'); var tls = require('tls'); +var utils = require('../utils'); function Connector(options) { this.options = options; @@ -34,7 +35,7 @@ Connector.prototype.connect = function (callback) { var _this = this; process.nextTick(function () { if (!_this.connecting) { - callback(new Error('Connection is closed.')); + callback(new Error(utils.CONNECTION_CLOSED_ERROR_MSG)); return; } var stream; diff --git a/lib/connectors/sentinel_connector.js b/lib/connectors/sentinel_connector.js index fe23aeea..a4bb5af9 100644 --- a/lib/connectors/sentinel_connector.js +++ b/lib/connectors/sentinel_connector.js @@ -68,7 +68,7 @@ SentinelConnector.prototype.connect = function (callback) { var endpoint = _this.sentinels[_this.currentPoint]; _this.resolve(endpoint, function (err, resolved) { if (!_this.connecting) { - callback(new Error('Connection is closed.')); + callback(new Error(utils.CONNECTION_CLOSED_ERROR_MSG)); return; } if (resolved) { @@ -166,6 +166,9 @@ SentinelConnector.prototype.resolve = function (endpoint, callback) { dropBufferSupport: true }); + // ignore the errors since resolve* methods will handle them + client.on('error', noop); + if (this.options.role === 'slave') { this.resolveSlave(client, callback); } else { @@ -173,4 +176,6 @@ SentinelConnector.prototype.resolve = function (endpoint, callback) { } }; +function noop() {} + module.exports = SentinelConnector; diff --git a/lib/redis.js b/lib/redis.js index 000f0914..2ad12be1 100644 --- a/lib/redis.js +++ b/lib/redis.js @@ -438,14 +438,33 @@ Redis.prototype._readyCheck = function (callback) { * @private */ Redis.prototype.silentEmit = function (eventName) { + var error; + if (eventName === 'error') { + error = arguments[1]; + + if (this.status === 'end') { + return; + } + + if (this.manuallyClosing) { + // ignore connection related errors when manually disconnecting + if ( + error instanceof Error && + ( + error.message === utils.CONNECTION_CLOSED_ERROR_MSG || + error.syscall === 'connect' || + error.syscall === 'read' + ) + ) { + return; + } + } + } if (this.listeners(eventName).length > 0) { return this.emit.apply(this, arguments); } - if (eventName === 'error') { - var error = arguments[1]; - if (error instanceof Error) { - console.error('[ioredis] Unhandled error event:', error.stack); - } + if (error && error instanceof Error) { + console.error('[ioredis] Unhandled error event:', error.stack); } return false; }; @@ -528,7 +547,7 @@ Redis.prototype.sendCommand = function (command, stream) { this.connect().catch(function () {}); } if (this.status === 'end') { - command.reject(new Error('Connection is closed.')); + command.reject(new Error(utils.CONNECTION_CLOSED_ERROR_MSG)); return command.promise; } if (this.condition.subscriber && !Command.checkFlag('VALID_IN_SUBSCRIBER_MODE', command.name)) { diff --git a/lib/redis/event_handler.js b/lib/redis/event_handler.js index 2031071b..250bafcc 100644 --- a/lib/redis/event_handler.js +++ b/lib/redis/event_handler.js @@ -2,6 +2,7 @@ var debug = require('debug')('ioredis:connection'); var Command = require('../command'); +var utils = require('../utils'); exports.connectHandler = function (self) { return function () { @@ -96,7 +97,7 @@ exports.closeHandler = function (self) { function close() { self.setStatus('end'); - self.flushQueue(new Error('Connection is closed.')); + self.flushQueue(new Error(utils.CONNECTION_CLOSED_ERROR_MSG)); } }; diff --git a/lib/utils/index.js b/lib/utils/index.js index 0eab9589..948fefff 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -295,3 +295,8 @@ exports.sample = function (array, from) { } return array[from + Math.floor(Math.random() * (length - from))]; }; + +/** + * Error message for connection being disconnected + */ +exports.CONNECTION_CLOSED_ERROR_MSG = 'Connection is closed.'; diff --git a/test/functional/connection.js b/test/functional/connection.js index 6002a6d2..ba3079c7 100644 --- a/test/functional/connection.js +++ b/test/functional/connection.js @@ -86,7 +86,7 @@ describe('connection', function () { }); var redis2 = new Redis(6390, { lazyConnect: true, retryStrategy: null }); - redis2.connect().catch(function (err) { + redis2.connect().catch(function () { if (!--pending) { redis2.disconnect(); done();