Skip to content
Browse files

Performance improvements and backpressure controls.

Simply and speed up command argument processing logic.
Commands now return the true/false value from the underlying socket write(s).
Implement command_queue high water and low water for more better control of queueing.
  • Loading branch information...
1 parent 1391429 commit f9e17556d26cdabd85d9e979d0ad1881ca589df6 @mranney mranney committed
Showing with 225 additions and 154 deletions.
  1. +1 −1 examples/monitor.js
  2. +154 −111 index.js
  3. +46 −32 lib/parser/javascript.js
  4. +5 −1 lib/queue.js
  5. +1 −2 lib/to_array.js
  6. +8 −4 multi_bench.js
  7. +10 −3 test.js
View
2 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) {
View
265 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));
View
78 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"));
View
6 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();
}
});
View
3 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;
View
12 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) {
View
13 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) {

0 comments on commit f9e1755

Please sign in to comment.
Something went wrong with that request. Please try again.