1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-06 02:15:48 +03:00

chore: refactor flush and error

This commit is contained in:
Ruben Bridgewater
2017-05-28 00:13:17 +02:00
parent 2aa3b68fc6
commit 8da9e98fe6
7 changed files with 95 additions and 94 deletions

View File

@@ -144,30 +144,28 @@ RedisClient.prototype.initializeRetryVars = function () {
} }
// Flush provided queues, erroring any items with a callback first // Flush provided queues, erroring any items with a callback first
RedisClient.prototype.flushAndError = function (errorAttributes, options) { RedisClient.prototype.flushAndError = function (message, code, options) {
options = options || {} options = options || {}
const queueNames = options.queues || ['commandQueue', 'offlineQueue'] // Flush the commandQueue first to keep the order intact const queueNames = options.queues || ['commandQueue', 'offlineQueue'] // Flush the commandQueue first to keep the order intact
for (var i = 0; i < queueNames.length; i++) { for (var i = 0; i < queueNames.length; i++) {
// If the command was fired it might have been processed so far // If the command was fired it might have been processed so far
if (queueNames[i] === 'commandQueue') { const ErrorClass = queueNames[i] === 'commandQueue'
errorAttributes.message += ' It might have been processed.' ? Errors.InterruptError
} else { // As the commandQueue is flushed first, remove this for the offline queue : Errors.AbortError
errorAttributes.message = errorAttributes.message.replace(' It might have been processed.', '')
} while (this[queueNames[i]].length) {
// Don't flush everything from the queue const command = this[queueNames[i]].shift()
for (var commandObj = this[queueNames[i]].shift(); commandObj; commandObj = this[queueNames[i]].shift()) { const err = new ErrorClass(message)
const err = new errorClasses.AbortError(errorAttributes) err.code = code
if (commandObj.error) { err.command = command.command.toUpperCase()
err.stack = err.stack + commandObj.error.stack.replace(/^Error.*?\n/, '\n') err.args = command.args
} if (command.error) {
err.command = commandObj.command.toUpperCase() err.stack = err.stack + command.error.stack.replace(/^Error.*?\n/, '\n')
if (commandObj.args && commandObj.args.length) {
err.args = commandObj.args
} }
if (options.error) { if (options.error) {
err.origin = options.error err.origin = options.error
} }
commandObj.callback(err) command.callback(err)
} }
} }
} }

View File

@@ -29,10 +29,7 @@ function createParser (client) {
// Note: the execution order is important. First flush and emit, then create the stream // Note: the execution order is important. First flush and emit, then create the stream
err.message += '. Please report this.' err.message += '. Please report this.'
client.ready = false client.ready = false
client.flushAndError({ client.flushAndError('Fatal error encountered. Command aborted.', 'NR_FATAL', {
message: 'Fatal error encountered. Command aborted.',
code: 'NR_FATAL'
}, {
error: err, error: err,
queues: ['commandQueue'] queues: ['commandQueue']
}) })

View File

@@ -46,10 +46,7 @@ RedisClient.prototype.end = function (flush) {
// Flush queue if wanted // Flush queue if wanted
if (flush) { if (flush) {
this.flushAndError({ this.flushAndError('Connection forcefully ended and command aborted.', 'NR_CLOSED')
message: 'Connection forcefully ended and command aborted.',
code: 'NR_CLOSED'
})
} }
// Clear retryTimer // Clear retryTimer
if (this.retryTimer) { if (this.retryTimer) {

View File

@@ -107,6 +107,12 @@ Multi.prototype.quit = function quit () {
return this return this
} }
/**
* @description Return a function that receives the raw info data and convert to an object.
*
* @param {RedisClient} client
* @returns {function}
*/
function infoCallback (client) { function infoCallback (client) {
return function (err, res) { return function (err, res) {
if (err) { if (err) {

View File

@@ -1,5 +1,6 @@
'use strict' 'use strict'
const Errors = require('redis-errors')
const Queue = require('denque') const Queue = require('denque')
const utils = require('./utils') const utils = require('./utils')
const Command = require('./command') const Command = require('./command')
@@ -136,7 +137,7 @@ function execBatch (multi) {
client.uncork() client.uncork()
return Promise.all(promises).then((res) => { return Promise.all(promises).then((res) => {
if (error) { if (error) {
const err = new Error('bla failed') const err = new Errors.RedisError('bla failed')
err.code = 'foo' err.code = 'foo'
err.replies = res err.replies = res
return Promise.reject(err) return Promise.reject(err)

View File

@@ -12,26 +12,41 @@ function onConnect (client) {
client._stream.setKeepAlive(client.options.socketKeepalive) client._stream.setKeepAlive(client.options.socketKeepalive)
client._stream.setTimeout(0) client._stream.setTimeout(0)
// TODO: Deprecate the connect event.
client.emit('connect') client.emit('connect')
client.initializeRetryVars() client.initializeRetryVars()
if (client.options.noReadyCheck) { if (client.options.noReadyCheck) {
onReady(client) readyHandler(client)
} else { } else {
readyCheck(client) readyCheck(client)
} }
} }
/**
* @description Empty the offline queue and call the commands
*
* @param {RedisClient} client
*/
function sendOfflineQueue (client) { function sendOfflineQueue (client) {
while (client.offlineQueue.length) { const queue = client.offlineQueue
const command = client.offlineQueue.shift() while (queue.length) {
const command = queue.shift()
debug('Sending offline command: %s', command.command) debug('Sending offline command: %s', command.command)
client.internalSendCommand(command) client.internalSendCommand(command)
} }
} }
function onReady (client) { /**
debug('onReady called %s id %s', client.address, client.connectionId) * @description Transparently perform predefined commands and emit ready.
*
* Emit ready before the all commands returned.
* The order of the commands is important.
*
* @param {RedisClient} client
*/
function readyHandler (client) {
debug('readyHandler called %s id %s', client.address, client.connectionId)
client.ready = true client.ready = true
client.cork = () => { client.cork = () => {
@@ -50,7 +65,6 @@ function onReady (client) {
client._stream.uncork() client._stream.uncork()
} }
// Restore modal commands from previous connection. The order of the commands is important
if (client.selectedDb !== undefined) { if (client.selectedDb !== undefined) {
client.internalSendCommand(new Command('select', [client.selectedDb])).catch((err) => { client.internalSendCommand(new Command('select', [client.selectedDb])).catch((err) => {
if (!client.closing) { if (!client.closing) {
@@ -78,7 +92,7 @@ function onReady (client) {
// // individual: function noop () {} // // individual: function noop () {}
// } // }
if (!client.options.disableResubscribing && callbackCount) { if (!client.options.disableResubscribing && callbackCount) {
debug('Sending pub/sub onReady commands') debug('Sending pub/sub commands')
for (const key in client.subscriptionSet) { for (const key in client.subscriptionSet) {
if (client.subscriptionSet.hasOwnProperty(key)) { if (client.subscriptionSet.hasOwnProperty(key)) {
const command = key.slice(0, key.indexOf('_')) const command = key.slice(0, key.indexOf('_'))
@@ -95,55 +109,54 @@ function onReady (client) {
client.emit('ready') client.emit('ready')
} }
function onInfoFail (client, err) { /**
if (client.closing) { * @description Perform a info command and check if Redis is ready
return *
} * @param {RedisClient} client
*/
if (err.message === "ERR unknown command 'info'") {
onReady(client)
return
}
err.message = `Ready check failed: ${err.message}`
client.emit('error', err)
return
}
function onInfoCmd (client, res) {
/* istanbul ignore if: some servers might not respond with any info data. client is just a safety check that is difficult to test */
if (!res) {
debug('The info command returned without any data.')
onReady(client)
return
}
if (!client.serverInfo.loading || client.serverInfo.loading === '0') {
// If the master_link_status exists but the link is not up, try again after 50 ms
if (client.serverInfo.master_link_status && client.serverInfo.master_link_status !== 'up') {
client.serverInfo.loading_eta_seconds = 0.05
} else {
// Eta loading should change
debug('Redis server ready.')
onReady(client)
return
}
}
var retryTime = +client.serverInfo.loading_eta_seconds * 1000
if (retryTime > 1000) {
retryTime = 1000
}
debug('Redis server still loading, trying again in %s', retryTime)
setTimeout((client) => readyCheck(client), retryTime, client)
}
function readyCheck (client) { function readyCheck (client) {
debug('Checking server ready state...') debug('Checking server ready state...')
// Always fire client info command as first command even if other commands are already queued up // Always fire client info command as first command even if other commands are already queued up
client.ready = true client.ready = true
client.info() client.info().then((res) => {
.then((res) => onInfoCmd(client, res)) /* istanbul ignore if: some servers might not respond with any info data. client is just a safety check that is difficult to test */
.catch((err) => onInfoFail(client, err)) if (!res) {
debug('The info command returned without any data.')
readyHandler(client)
return
}
if (!client.serverInfo.loading || client.serverInfo.loading === '0') {
// If the master_link_status exists but the link is not up, try again after 50 ms
if (client.serverInfo.master_link_status && client.serverInfo.master_link_status !== 'up') {
client.serverInfo.loading_eta_seconds = 0.05
} else {
// Eta loading should change
debug('Redis server ready.')
readyHandler(client)
return
}
}
var retryTime = +client.serverInfo.loading_eta_seconds * 1000
if (retryTime > 1000) {
retryTime = 1000
}
debug('Redis server still loading, trying again in %s', retryTime)
setTimeout((client) => readyCheck(client), retryTime, client)
}).catch((err) => {
if (client.closing) {
return
}
if (err.message === "ERR unknown command 'info'") {
readyHandler(client)
return
}
err.message = `Ready check failed: ${err.message}`
client.emit('error', err)
return
})
client.ready = false client.ready = false
} }

View File

@@ -1,5 +1,6 @@
'use strict' 'use strict'
const Errors = require('redis-errors')
const debug = require('./debug') const debug = require('./debug')
var lazyConnect = function (client) { var lazyConnect = function (client) {
lazyConnect = require('./connect') lazyConnect = require('./connect')
@@ -63,13 +64,10 @@ function reconnect (client, why, error) {
if (why === 'timeout') { if (why === 'timeout') {
var message = 'Redis connection in broken state: connection timeout exceeded.' var message = 'Redis connection in broken state: connection timeout exceeded.'
const err = new Error(message) const err = new Errors.RedisError(message)
// TODO: Find better error codes... // TODO: Find better error codes...
err.code = 'CONNECTION_BROKEN' err.code = 'CONNECTION_BROKEN'
client.flushAndError({ client.flushAndError(message, 'CONNECTION_BROKEN')
message: message,
code: 'CONNECTION_BROKEN'
})
client.emit('error', err) client.emit('error', err)
client.end(false) client.end(false)
return return
@@ -78,10 +76,7 @@ function reconnect (client, why, error) {
// If client is a requested shutdown, then don't retry // If client is a requested shutdown, then don't retry
if (client.closing) { if (client.closing) {
debug('Connection ended by quit / end command, not retrying.') debug('Connection ended by quit / end command, not retrying.')
client.flushAndError({ client.flushAndError('Stream connection ended and command aborted.', 'NR_CLOSED', {
message: 'Stream connection ended and command aborted.',
code: 'NR_CLOSED'
}, {
error error
}) })
return return
@@ -98,10 +93,7 @@ function reconnect (client, why, error) {
if (client.retryDelay instanceof Error) { if (client.retryDelay instanceof Error) {
error = client.retryDelay error = client.retryDelay
} }
client.flushAndError({ client.flushAndError('Stream connection ended and command aborted.', 'NR_CLOSED', {
message: 'Stream connection ended and command aborted.',
code: 'NR_CLOSED'
}, {
error error
}) })
// TODO: Check if client is so smart // TODO: Check if client is so smart
@@ -117,10 +109,7 @@ function reconnect (client, why, error) {
client.offlineQueue.unshift.apply(client.offlineQueue, client.commandQueue.toArray()) client.offlineQueue.unshift.apply(client.offlineQueue, client.commandQueue.toArray())
client.commandQueue.clear() client.commandQueue.clear()
} else if (client.commandQueue.length !== 0) { } else if (client.commandQueue.length !== 0) {
client.flushAndError({ client.flushAndError('Redis connection lost and command aborted.', 'UNCERTAIN_STATE', {
message: 'Redis connection lost and command aborted.',
code: 'UNCERTAIN_STATE'
}, {
error, error,
queues: ['commandQueue'] queues: ['commandQueue']
}) })