Command.prototype._iterateKeys = function (transform) { if (typeof this._keys === 'undefined') { if (typeof transform !== 'function') { transform = function (key) { return key; }; } this._keys = []; if (commands.exists(this.name)) { var keyIndexes = commands.getKeyIndexes(this.name, this.args); for (var i = 0; i < keyIndexes.length; i++) { var index = keyIndexes[i]; this.args[index] = transform(this.args[index]); this._keys.push(this.args[index]); } } } return this._keys; };
Pipeline.prototype.fillResult = function (value, position) { var i; if (this._queue[position].name === 'exec' && Array.isArray(value[1])) { var execLength = value[1].length; for (i = 0; i < execLength; i++) { if (value[1][i] instanceof Error) { continue; } var cmd = this._queue[position - (execLength - i)]; try { value[1][i] = cmd.transformReply(value[1][i]); } catch (err) { value[1][i] = err; } } } this._result[position] = value; if (--this.replyPending) { return; } if (this.isCluster) { var retriable = true; var commonError; var inTransaction; for (i = 0; i < this._result.length; ++i) { var error = this._result[i][0]; var command = this._queue[i]; if (command.name === 'multi') { inTransaction = true; } else if (command.name === 'exec') { inTransaction = false; } if (error) { if (command.name === 'exec' && error.message === 'EXECABORT Transaction discarded because of previous errors.') { continue; } if (!commonError) { commonError = { name: error.name, message: error.message }; } else if (commonError.name !== error.name || commonError.message !== error.message) { retriable = false; break; } } else if (!inTransaction) { var isReadOnly = commands.exists(command.name) && commands.hasFlag(command.name, 'readonly'); if (!isReadOnly) { retriable = false; break; } } } if (commonError && retriable) { var _this = this; var errv = commonError.message.split(' '); var queue = this._queue; inTransaction = false; this._queue = []; for (i = 0; i < queue.length; ++i) { if (errv[0] === 'ASK' && !inTransaction && queue[i].name !== 'asking' && (!queue[i - 1] || queue[i - 1].name !== 'asking')) { var asking = new Command('asking'); asking.ignore = true; this.sendCommand(asking); } queue[i].initPromise(); this.sendCommand(queue[i]); if (queue[i].name === 'multi') { inTransaction = true; } else if (queue[i].name === 'exec') { inTransaction = false; } } var matched = true; if (typeof this.leftRedirections === 'undefined') { this.leftRedirections = {}; } var exec = function () { _this.exec(); }; this.redis.handleError(commonError, this.leftRedirections, { moved: function (slot, key) { _this.preferKey = key; _this.redis.slots[errv[1]] = [key]; _this.redis.refreshSlotsCache(); _this.exec(); }, ask: function (slot, key) { _this.preferKey = key; _this.exec(); }, tryagain: exec, clusterDown: exec, connectionClosed: exec, maxRedirections: function () { matched = false; }, defaults: function () { matched = false; } }); if (matched) { return; } } } var ignoredCount = 0; for (i = 0; i < this._queue.length - ignoredCount; ++i) { if (this._queue[i + ignoredCount].ignore) { ignoredCount += 1; } this._result[i] = this._result[i + ignoredCount]; } this.resolve(this._result.slice(0, this._result.length - ignoredCount)); };
Cluster.prototype.sendCommand = function (command, stream, node) { if (this.status === 'end') { command.reject(new Error('Connection is closed.')); return command.promise; } var to = this.options.scaleReads; if (to !== 'master') { var isCommandReadOnly = commands.exists(command.name) && commands.hasFlag(command.name, 'readonly'); if (!isCommandReadOnly) { to = 'master'; } } var targetSlot = node ? node.slot : command.getSlot(); var ttl = {}; var _this = this; if (!node && !command.__is_reject_overwritten) { command.__is_reject_overwritten = true; var reject = command.reject; command.reject = function (err) { var partialTry = _.partial(tryConnection, true); _this.handleError(err, ttl, { moved: function (slot, key) { debug('command %s is moved to %s', command.name, key); if (_this.slots[slot]) { _this.slots[slot][0] = key; } else { _this.slots[slot] = [key]; } var splitKey = key.split(':'); _this.connectionPool.findOrCreate({ host: splitKey[0], port: Number(splitKey[1]) }); tryConnection(); _this.refreshSlotsCache(); }, ask: function (slot, key) { debug('command %s is required to ask %s:%s', command.name, key); tryConnection(false, key); }, tryagain: partialTry, clusterDown: partialTry, connectionClosed: partialTry, maxRedirections: function (redirectionError) { reject.call(command, redirectionError); }, defaults: function () { reject.call(command, err); } }); }; } tryConnection(); function tryConnection(random, asking) { if (_this.status === 'end') { command.reject(new Error('Cluster is ended.')); return; } var redis; if (_this.status === 'ready' || (command.name === 'cluster')) { if (node && node.redis) { redis = node.redis; } else if (Command.checkFlag('ENTER_SUBSCRIBER_MODE', command.name) || Command.checkFlag('EXIT_SUBSCRIBER_MODE', command.name)) { redis = _this.subscriber; } else { if (!random) { if (typeof targetSlot === 'number' && _this.slots[targetSlot]) { var nodeKeys = _this.slots[targetSlot]; if (typeof to === 'function') { var nodes = nodeKeys .map(function (key) { return _this.connectionPool.nodes.all[key]; }); redis = to(nodes, command); if (Array.isArray(redis)) { redis = utils.sample(redis); } if (!redis) { redis = nodes[0]; } } else { var key; if (to === 'all') { key = utils.sample(nodeKeys); } else if (to === 'slave' && nodeKeys.length > 1) { key = utils.sample(nodeKeys, 1); } else { key = nodeKeys[0]; } redis = _this.connectionPool.nodes.all[key]; } } if (asking) { redis = _this.connectionPool.nodes.all[asking]; redis.asking(); } } if (!redis) { redis = _.sample(_this.connectionPool.nodes[to]) || _.sample(_this.connectionPool.nodes.all); } } if (node && !node.redis) { node.redis = redis; } } if (redis) { redis.sendCommand(command, stream); } else if (_this.options.enableOfflineQueue) { _this.offlineQueue.push({ command: command, stream: stream, node: node }); } else { command.reject(new Error('Cluster isn\'t ready and enableOfflineQueue options is false')); } } return command.promise; };