diff --git a/examples/monitor.js b/examples/monitor.js index e728d62..2cb6a4e 100644 --- a/examples/monitor.js +++ b/examples/monitor.js @@ -1,4 +1,4 @@ -var client = require("redis").createClient(), +var client = require("../index").createClient(), util = require("util"); client.monitor(function (err, res) { diff --git a/index.js b/index.js index 411ce23..7315bc7 100644 --- a/index.js +++ b/index.js @@ -32,6 +32,9 @@ function RedisClient(stream, options) { this.ready = false; this.connections = 0; this.attempts = 1; + this.should_buffer = false; + this.command_queue_high_water = this.options.command_queue_high_water || 1000; + this.command_queue_low_water = this.options.command_queue_low_water || 0; this.command_queue = new Queue(); // holds sent commands to de-pipeline them this.offline_queue = new Queue(); // holds commands issued but not able to be sent this.commands_sent = 0; @@ -132,6 +135,7 @@ function RedisClient(stream, options) { }); this.stream.on("drain", function () { + self.should_buffer = false; self.emit("drain"); }); @@ -157,26 +161,21 @@ RedisClient.prototype.on_connect = function () { this.stream.setNoDelay(); this.stream.setTimeout(0); - if (this.auth_pass) { - var self = this; - - // if redis is still loading the db, it will not authenticate and everything else will fail - function cmd_do_auth() { - + // if redis is still loading the db, it will not authenticate and everything else will fail + function cmd_do_auth() { if (exports.debug_mode) { console.log("Sending auth to " + self.host + ":" + self.port + " fd " + self.stream.fd); } self.send_anyway = true; - self.send_command("auth", self.auth_pass, function (err, res) { + self.send_command("auth", [this.auth_pass], function (err, res) { if (err) { if (err.toString().match("LOADING")) { - // still loading, try to authenticate later - - console.log("Redis still loading, trying to authenticate later"); - setTimeout(cmd_do_auth,2000); - return; + console.log("Redis still loading, trying to authenticate later"); + setTimeout(cmd_do_auth, 2000); // TODO - magic number alert + return; + } else { + return self.emit("error", "Auth error: " + err); } - else return self.emit("error", "Auth error: " + err); } if (res.toString() !== "OK") { return self.emit("error", "Auth failed: " + res.toString()); @@ -191,31 +190,28 @@ RedisClient.prototype.on_connect = function () { // now we are really connected self.emit("connect"); - - if (self.options.no_ready_check) { - self.ready = true; - self.send_offline_queue(); + if (self.options.no_ready_check) { + self.ready = true; + self.send_offline_queue(); } else { - self.ready_check(); + self.ready_check(); } - }); self.send_anyway = false; - } + } - cmd_do_auth(); + if (this.auth_pass) { + cmd_do_auth(); } else { + this.emit("connect"); - this.emit("connect"); - - if (this.options.no_ready_check) { - this.ready = true; - this.send_offline_queue(); - } else { - this.ready_check(); - } + if (this.options.no_ready_check) { + this.ready = true; + this.send_offline_queue(); + } else { + this.ready_check(); + } } - }; RedisClient.prototype.ready_check = function () { @@ -275,16 +271,21 @@ RedisClient.prototype.ready_check = function () { }; RedisClient.prototype.send_offline_queue = function () { - var command_obj; + var command_obj, buffered_writes = 0; while (this.offline_queue.length > 0) { command_obj = this.offline_queue.shift(); if (exports.debug_mode) { console.log("Sending offline command: " + command_obj.command); } - this.send_command(command_obj.command, command_obj.args, command_obj.callback); + buffered_writes += !this.send_command(command_obj.command, command_obj.args, command_obj.callback); } this.offline_queue = new Queue(); - // Even though items were shifted off, Queue backing store still uses memory until next add + // Even though items were shifted off, Queue backing store still uses memory until next add, so just get a new Queue + + if (buffered_writes === 0) { + this.should_buffer = false; + this.emit("drain"); + } }; RedisClient.prototype.connection_gone = function (why) { @@ -361,12 +362,16 @@ RedisClient.prototype.on_data = function (data) { }; RedisClient.prototype.return_error = function (err) { - var command_obj = this.command_queue.shift(); + var command_obj = this.command_queue.shift(), queue_len = this.command_queue.getLength(); - if (this.subscriptions === false && this.command_queue.length === 0) { + if (this.subscriptions === false && queue_len === 0) { this.emit("idle"); this.command_queue = new Queue(); } + if (this.should_buffer && queue_len <= this.command_queue_low_water) { + this.emit("drain"); + this.should_buffer = false; + } if (command_obj && typeof command_obj.callback === "function") { try { @@ -388,11 +393,15 @@ RedisClient.prototype.return_error = function (err) { RedisClient.prototype.return_reply = function (reply) { var command_obj = this.command_queue.shift(), - obj, i, len, key, val, type, timestamp, args; + obj, i, len, key, val, type, timestamp, args, queue_len = this.command_queue.getLength(); - if (this.subscriptions === false && this.command_queue.length === 0) { + if (this.subscriptions === false && queue_len === 0) { this.emit("idle"); - this.command_queue = new Queue(); + this.command_queue = new Queue(); // explicitly reclaim storage from old Queue + } + if (this.should_buffer && queue_len <= this.command_queue_low_water) { + this.emit("drain"); + this.should_buffer = false; } if (command_obj && !command_obj.sub_command) { @@ -453,52 +462,56 @@ RedisClient.prototype.return_reply = function (reply) { } }; -RedisClient.prototype.send_command = function () { - var command, callback, arg, args, this_args, command_obj, i, il, - elem_count, stream = this.stream, buffer_args, command_str = ""; - - this_args = to_array(arguments); +// This Command constructor is ever so slightly faster than using an object literal +function Command(command, args, sub_command, callback) { + this.command = command; + this.args = args; + this.sub_command = sub_command; + this.callback = callback; +} - if (this_args.length === 0) { - throw new Error("send_command: not enough arguments"); - } +RedisClient.prototype.send_command = function (command, args, callback) { + var arg, this_args, command_obj, i, il, elem_count, stream = this.stream, buffer_args, command_str = "", buffered_writes = 0; - if (typeof this_args[0] !== "string") { - throw new Error("First argument of send_command must be the command name"); + if (typeof command !== "string") { + throw new Error("First argument to send_command must be the command name string, not " + typeof command); } - command = this_args[0].toLowerCase(); - if (this_args[1] && Array.isArray(this_args[1])) { - args = this_args[1]; - if (typeof this_args[2] === "function") { - callback = this_args[2]; - } - } else { - if (typeof this_args[this_args.length - 1] === "function") { - callback = this_args[this_args.length - 1]; - args = this_args.slice(1, this_args.length - 1); + if (Array.isArray(args)) { + if (typeof callback === "function") { + // probably the fastest way: + // client.command([arg1, arg2], cb); (straight passthrough) + // send_command(command, [arg1, arg2], cb); + } else if (typeof callback === "undefined") { + // most people find this variable argument length form more convenient, but it uses arguments, which is slower + // client.command(arg1, arg2, cb); (wraps up arguments into an array) + // send_command(command, [arg1, arg2, cb]); + // client.command(arg1, arg2); (callback is optional) + // send_command(command, [arg1, arg2]); + if (typeof args[args.length - 1] === "function") { + callback = args[args.length - 1]; + args.length -= 1; + } } else { - args = this_args.slice(1, this_args.length); + throw new Error("send_command: last argument must be a callback or undefined"); } + } else { + throw new Error("send_command: second argument must be an array"); } - if (args.length === 2 && Array.isArray(args[1])) { - args = [args[0]].concat(args[1]); - } - - command_obj = { - command: command, - args: args, - callback: callback, - sub_command: false - }; + command_obj = new Command(command, args, false, callback); - if (!this.ready && !this.send_anyway) { + if ((!this.ready && !this.send_anyway) || !stream.writable) { if (exports.debug_mode) { + if (!stream.writable) { + console.log("send command: stream is not writeable."); + } + console.log("Queueing " + command + " for next server connection."); } this.offline_queue.push(command_obj); - return; + this.should_buffer = true; + return false; } if (command === "subscribe" || command === "psubscribe" || command === "unsubscribe" || command === "punsubscribe") { @@ -521,10 +534,6 @@ RedisClient.prototype.send_command = function () { buffer_args = false; elem_count += args.length; - // Probably should just scan this like a normal person. This is clever, but might be slow. - buffer_args = args.some(function (arg) { - return arg instanceof Buffer; - }); // Always use "Multi bulk commands", but if passed any Buffer args, then do multiple writes, one for each arg // This means that using Buffers in commands is going to be slower, so use Strings if you don't already have a Buffer. @@ -532,9 +541,10 @@ RedisClient.prototype.send_command = function () { command_str = "*" + elem_count + "\r\n$" + command.length + "\r\n" + command + "\r\n"; - if (! stream.writable && exports.debug_mode) { - console.log("send command: stream is not writeable, should get a close event next tick."); - return; + for (i = 0, il = args.length, arg; i < il; i += 1) { + if (args[i] instanceof Buffer) { + buffer_args = true; + } } if (! buffer_args) { // Build up a string and send entire command in one write @@ -548,13 +558,12 @@ RedisClient.prototype.send_command = function () { if (exports.debug_mode) { console.log("send " + this.host + ":" + this.port + " fd " + this.stream.fd + ": " + command_str); } - stream.write(command_str); + buffered_writes += !stream.write(command_str); } else { if (exports.debug_mode) { - console.log("send command: " + command_str); - console.log("send command has Buffer arguments"); + console.log("send command (" + command_str + ") has Buffer arguments"); } - stream.write(command_str); + buffered_writes += !stream.write(command_str); for (i = 0, il = args.length, arg; i < il; i += 1) { arg = args[i]; @@ -565,19 +574,32 @@ RedisClient.prototype.send_command = function () { if (arg instanceof Buffer) { if (arg.length === 0) { if (exports.debug_mode) { - console.log("Using empty string for 0 length buffer"); + console.log("send_command: using empty string for 0 length buffer"); } - stream.write("$0\r\n\r\n"); + buffered_writes += !stream.write("$0\r\n\r\n"); } else { - stream.write("$" + arg.length + "\r\n"); - stream.write(arg); - stream.write("\r\n"); + buffered_writes += !stream.write("$" + arg.length + "\r\n"); + buffered_writes += !stream.write(arg); + buffered_writes += !stream.write("\r\n"); + if (exports.debug_mode) { + console.log("send_command: buffer send " + arg.length + " bytes"); + } } } else { - stream.write("$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n"); + if (exports.debug_mode) { + console.log("send_command: string send " + Buffer.byteLength(arg) + " bytes: " + arg); + } + buffered_writes += !stream.write("$" + Buffer.byteLength(arg) + "\r\n" + arg + "\r\n"); } } } + if (exports.debug_mode) { + console.log("send_command buffered_writes: " + buffered_writes, " should_buffer: " + this.should_buffer); + } + if (buffered_writes || this.command_queue.getLength() >= this.command_queue_high_water) { + this.should_buffer = true; + } + return !this.should_buffer; }; RedisClient.prototype.end = function () { @@ -587,7 +609,6 @@ RedisClient.prototype.end = function () { return this.stream.end(); }; - function Multi(client, args) { this.client = client; this.queue = [["MULTI"]]; @@ -622,17 +643,17 @@ commands = set_union(["get", "set", "setnx", "setex", "append", "strlen", "del", "restore", "migrate", "dump", "object", "client", "eval", "evalsha"], require("./lib/commands")); commands.forEach(function (command) { - RedisClient.prototype[command] = function () { - var args = to_array(arguments); - args.unshift(command); // put command at the beginning - this.send_command.apply(this, args); + RedisClient.prototype[command] = function (args, callback) { + if (Array.isArray(args) && typeof callback === "function") { + return this.send_command(command, args, callback); + } else { + return this.send_command(command, to_array(arguments)); + } }; RedisClient.prototype[command.toUpperCase()] = RedisClient.prototype[command]; Multi.prototype[command] = function () { - var args = to_array(arguments); - args.unshift(command); - this.queue.push(args); + this.queue.push([command].concat(to_array(arguments))); return this; }; Multi.prototype[command.toUpperCase()] = Multi.prototype[command]; @@ -648,29 +669,50 @@ RedisClient.prototype.auth = function () { } if (this.connected) { - args.unshift("auth"); - this.send_command.apply(this, args); + this.send_command("auth", args); } }; RedisClient.prototype.AUTH = RedisClient.prototype.auth; -RedisClient.prototype.hmset = function () { - var args = to_array(arguments), tmp_args; - if (args.length >= 2 && typeof args[0] === "string" && typeof args[1] === "object") { - tmp_args = [ "hmset", args[0] ]; - Object.keys(args[1]).map(function (key) { +RedisClient.prototype.hmget = function (arg1, arg2, arg3) { + if (Array.isArray(arg2) && typeof arg3 === "function") { + return this.send_command("hmget", [arg1].concat(arg2), arg3); + } else if (Array.isArray(arg1) && typeof arg2 === "function") { + return this.send_command("hmget", arg1, arg2); + } else { + return this.send_command("hmget", to_array(arguments)); + } +}; +RedisClient.prototype.HMGET = RedisClient.prototype.hmget; + +RedisClient.prototype.hmset = function (args, callback) { + var tmp_args, tmp_keys, i, il, key; + + if (Array.isArray(args) && typeof callback === "function") { + return this.send_command("hmset", args, callback); + } + + args = to_array(arguments); + if (typeof args[args.length - 1] === "function") { + callback = args[args.length - 1]; + args.length -= 1; + } else { + callback = null; + } + + if (args.length === 2 && typeof args[0] === "string" && typeof args[1] === "object") { + // User does: client.hmset(key, {key1: val1, key2: val2}) + tmp_args = [ args[0] ]; + tmp_keys = Object.keys(args[1]); + for (i = 0, il = tmp_keys.length; i < il ; i++) { + key = tmp_keys[i]; tmp_args.push(key); tmp_args.push(args[1][key]); - }); - if (args[2]) { - tmp_args.push(args[2]); } args = tmp_args; - } else { - args.unshift("hmset"); } - this.send_command.apply(this, args); + return this.send_command("hmset", args, callback); }; RedisClient.prototype.HMSET = RedisClient.prototype.hmset; @@ -699,7 +741,7 @@ Multi.prototype.exec = function (callback) { var self = this; // drain queue, callback will catch "QUEUED" or error - // Can't use a for loop here, as we need closure around the index. + // TODO - get rid of all of these anonymous functions which are elegant but slow this.queue.forEach(function (args, index) { var command = args[0], obj; if (typeof args[args.length - 1] === "function") { @@ -730,7 +772,8 @@ Multi.prototype.exec = function (callback) { }); }, this); - this.client.send_command("EXEC", function (err, replies) { + // TODO - make this callback part of Multi.prototype instead of creating it each time + return this.client.send_command("EXEC", [], function (err, replies) { if (err) { if (callback) { callback(new Error(err)); diff --git a/lib/parser/javascript.js b/lib/parser/javascript.js index d5755e9..6f250c9 100644 --- a/lib/parser/javascript.js +++ b/lib/parser/javascript.js @@ -35,8 +35,6 @@ function small_toString(buf, len) { // Reset parser to it's original state. RedisReplyParser.prototype.reset = function () { - this.state = "type"; - this.return_buffer = new Buffer(16384); // for holding replies, might grow this.return_string = ""; this.tmp_string = ""; // for holding size fields @@ -46,6 +44,22 @@ RedisReplyParser.prototype.reset = function () { this.multi_bulk_pos = 0; this.multi_bulk_nested_length = 0; this.multi_bulk_nested_replies = null; + + this.states = { + TYPE: 1, + SINGLE_LINE: 2, + MULTI_BULK_COUNT: 3, + INTEGER_LINE: 4, + BULK_LENGTH: 5, + ERROR_LINE: 6, + BULK_DATA: 7, + UNKNOWN_TYPE: 8, + FINAL_CR: 9, + FINAL_LF: 10, + MULTI_BULK_COUNT_LF: 11 + }; + + this.state = this.states.TYPE; }; RedisReplyParser.prototype.parser_error = function (message) { @@ -54,7 +68,7 @@ RedisReplyParser.prototype.parser_error = function (message) { }; RedisReplyParser.prototype.execute = function (incoming_buf) { - var pos = 0, bd_tmp, bd_str, i, il; + var pos = 0, bd_tmp, bd_str, i, il, states = this.states; //, state_times = {}, start_execute = new Date(), start_switch, end_switch, old_state; //start_switch = new Date(); @@ -63,76 +77,76 @@ RedisReplyParser.prototype.execute = function (incoming_buf) { // console.log("execute: " + this.state + ", " + pos + "/" + incoming_buf.length + ", " + String.fromCharCode(incoming_buf[pos])); switch (this.state) { - case "type": + case states.TYPE: this.type = incoming_buf[pos]; pos += 1; switch (this.type) { case 43: // + - this.state = "single line"; + this.state = states.SINGLE_LINE; this.return_buffer.end = 0; this.return_string = ""; break; case 42: // * - this.state = "multi bulk count"; + this.state = states.MULTI_BULK_COUNT; this.tmp_string = ""; break; case 58: // : - this.state = "integer line"; + this.state = states.INTEGER_LINE; this.return_buffer.end = 0; this.return_string = ""; break; case 36: // $ - this.state = "bulk length"; + this.state = states.BULK_LENGTH; this.tmp_string = ""; break; case 45: // - - this.state = "error line"; + this.state = states.ERROR_LINE; this.return_buffer.end = 0; this.return_string = ""; break; default: - this.state = "unknown type"; + this.state = states.UNKNOWN_TYPE; } break; - case "integer line": + case states.INTEGER_LINE: if (incoming_buf[pos] === 13) { this.send_reply(+small_toString(this.return_buffer, this.return_buffer.end)); - this.state = "final lf"; + this.state = states.FINAL_LF; } else { this.return_buffer[this.return_buffer.end] = incoming_buf[pos]; this.return_buffer.end += 1; } pos += 1; break; - case "error line": + case states.ERROR_LINE: if (incoming_buf[pos] === 13) { this.send_error(this.return_buffer.toString("ascii", 0, this.return_buffer.end)); - this.state = "final lf"; + this.state = states.FINAL_LF; } else { this.return_buffer[this.return_buffer.end] = incoming_buf[pos]; this.return_buffer.end += 1; } pos += 1; break; - case "single line": + case states.SINGLE_LINE: if (incoming_buf[pos] === 13) { this.send_reply(this.return_string); - this.state = "final lf"; + this.state = states.FINAL_LF; } else { this.return_string += String.fromCharCode(incoming_buf[pos]); } pos += 1; break; - case "multi bulk count": + case states.MULTI_BULK_COUNT: if (incoming_buf[pos] === 13) { // \r - this.state = "multi bulk count lf"; + this.state = states.MULTI_BULK_COUNT_LF; } else { this.tmp_string += String.fromCharCode(incoming_buf[pos]); } pos += 1; break; - case "multi bulk count lf": + case states.MULTI_BULK_COUNT_LF: if (incoming_buf[pos] === 10) { // \n if (this.multi_bulk_length) { // nested multi-bulk this.multi_bulk_nested_length = this.multi_bulk_length; @@ -141,7 +155,7 @@ RedisReplyParser.prototype.execute = function (incoming_buf) { } this.multi_bulk_length = +this.tmp_string; this.multi_bulk_pos = 0; - this.state = "type"; + this.state = states.TYPE; if (this.multi_bulk_length < 0) { this.send_reply(null); this.multi_bulk_length = 0; @@ -158,25 +172,25 @@ RedisReplyParser.prototype.execute = function (incoming_buf) { } pos += 1; break; - case "bulk length": + case states.BULK_LENGTH: if (incoming_buf[pos] === 13) { // \r - this.state = "bulk lf"; + this.state = states.BULK_LF; } else { this.tmp_string += String.fromCharCode(incoming_buf[pos]); } pos += 1; break; - case "bulk lf": + case states.BULK_LF: if (incoming_buf[pos] === 10) { // \n this.bulk_length = +this.tmp_string; if (this.bulk_length === -1) { this.send_reply(null); - this.state = "type"; + this.state = states.TYPE; } else if (this.bulk_length === 0) { this.send_reply(new Buffer("")); - this.state = "final cr"; + this.state = states.FINAL_CR; } else { - this.state = "bulk data"; + this.state = states.BULK_DATA; if (this.bulk_length > this.return_buffer.length) { if (exports.debug_mode) { console.log("Growing return_buffer from " + this.return_buffer.length + " to " + this.bulk_length); @@ -191,7 +205,7 @@ RedisReplyParser.prototype.execute = function (incoming_buf) { } pos += 1; break; - case "bulk data": + case states.BULK_DATA: this.return_buffer[this.return_buffer.end] = incoming_buf[pos]; this.return_buffer.end += 1; pos += 1; @@ -206,21 +220,21 @@ RedisReplyParser.prototype.execute = function (incoming_buf) { } } this.send_reply(bd_tmp); - this.state = "final cr"; + this.state = states.FINAL_CR; } break; - case "final cr": + case states.FINAL_CR: if (incoming_buf[pos] === 13) { // \r - this.state = "final lf"; + this.state = states.FINAL_LF; pos += 1; } else { this.parser_error(new Error("saw " + incoming_buf[pos] + " when expecting final CR")); return; } break; - case "final lf": + case states.FINAL_LF: if (incoming_buf[pos] === 10) { // \n - this.state = "type"; + this.state = states.TYPE; pos += 1; } else { this.parser_error(new Error("saw " + incoming_buf[pos] + " when expecting final LF")); diff --git a/lib/queue.js b/lib/queue.js index 50679de..5cc3c42 100644 --- a/lib/queue.js +++ b/lib/queue.js @@ -45,9 +45,13 @@ Queue.prototype.forEach = function (fn, thisv) { return array; }; +Queue.prototype.getLength = function () { + return this.head.length - this.offset + this.tail.length; +}; + Object.defineProperty(Queue.prototype, 'length', { get: function () { - return this.head.length - this.offset + this.tail.length; + return this.getLength(); } }); diff --git a/lib/to_array.js b/lib/to_array.js index 31e36bd..88a57e1 100644 --- a/lib/to_array.js +++ b/lib/to_array.js @@ -1,4 +1,3 @@ -// the "new Array(len)" syntax is legal and optimized by V8, but JSHint is utterly confused by it. function to_array(args) { var len = args.length, arr = new Array(len), i; @@ -8,6 +7,6 @@ function to_array(args) { } return arr; -}; +} module.exports = to_array; diff --git a/multi_bench.js b/multi_bench.js index 35b20db..b78c126 100644 --- a/multi_bench.js +++ b/multi_bench.js @@ -1,12 +1,12 @@ var redis = require("./index"), - num_clients = parseInt(process.argv[2]) || 50, + num_clients = parseInt(process.argv[2], 10) || 50, active_clients = 0, clients = new Array(num_clients), num_requests = 20000, issued_requests = 0, latency = new Array(num_requests), tests = [], - test_start, + test_start, parser_logged = false, client_options = { return_buffers: false }; @@ -35,7 +35,7 @@ tests.push({ tests.push({ descr: "LPUSH", - command: ["lpush", "mylist", Array(8).join("-")] + command: ["lpush", "mylist", new Array(8).join("-")] }); tests.push({ @@ -58,7 +58,11 @@ function create_clients(callback) { while (active_clients < num_clients) { client = clients[active_clients++] = redis.createClient(6379, "127.0.0.1", client_options); - client.on("connect", function() { + if (! parser_logged) { + console.log("Using reply parser " + client.reply_parser.name); + parser_logged = true; + } + client.on("connect", function () { // Fire callback when all clients are connected connected += 1; if (connected === num_clients) { diff --git a/test.js b/test.js index 4e13ca7..32e666c 100644 --- a/test.js +++ b/test.js @@ -384,7 +384,7 @@ tests.HSET = function () { }; tests.HMSET_BUFFER_AND_ARRAY = function () { - // Saving a buffer and an array to the same document should not error + // Saving a buffer and an array to the same key should not error var key = "test hash", field1 = "buffer", value1 = new Buffer("abcdefghij"), @@ -596,6 +596,13 @@ tests.GETSET = function () { tests.MGET = function () { var name = "MGET"; client.mset(["mget keys 1", "mget val 1", "mget keys 2", "mget val 2", "mget keys 3", "mget val 3"], require_string("OK", name)); + client.MGET("mget keys 1", "mget keys 2", "mget keys 3", function (err, results) { + assert.strictEqual(null, err, "result sent back unexpected error: " + err); + assert.strictEqual(3, results.length, name); + assert.strictEqual("mget val 1", results[0].toString(), name); + assert.strictEqual("mget val 2", results[1].toString(), name); + assert.strictEqual("mget val 3", results[2].toString(), name); + }); client.MGET(["mget keys 1", "mget keys 2", "mget keys 3"], function (err, results) { assert.strictEqual(null, err, "result sent back unexpected error: " + err); assert.strictEqual(3, results.length, name); @@ -647,7 +654,6 @@ tests.HGETALL = function () { client.HGETALL(["hosts"], function (err, obj) { assert.strictEqual(null, err, name + " result sent back unexpected error: " + err); assert.strictEqual(3, Object.keys(obj).length, name); -// assert.ok(Buffer.isBuffer(obj.mjr), name); assert.strictEqual("1", obj.mjr.toString(), name); assert.strictEqual("23", obj.another.toString(), name); assert.strictEqual("1234", obj.home.toString(), name); @@ -1216,7 +1222,8 @@ client.on("reconnecting", function (params) { }); process.on('uncaughtException', function (err) { - console.log("Uncaught exception: " + err.stack); + console.error("Uncaught exception: " + err.stack); + process.exit(1); }); process.on('exit', function (code) {