diff --git a/README.md b/README.md index 55e2ee6169..269ed19aa6 100644 --- a/README.md +++ b/README.md @@ -173,10 +173,6 @@ If false is returned the stream had to buffer. `client` will emit `warning` when password was set but none is needed and if a deprecated option / function / similar is used. -### "idle" (deprecated) - -`client` will emit `idle` when there are no outstanding commands that are awaiting a response. - ## redis.createClient() If you have `redis-server` running on the same machine as node, then the defaults for port and host are probably fine and you don't need to supply any arguments. `createClient()` returns a `RedisClient` object. Otherwise, `createClient()` accepts these arguments: diff --git a/index.js b/index.js index e1416123b5..1f83c3f585 100644 --- a/index.js +++ b/index.js @@ -115,12 +115,7 @@ function RedisClient (options, stream) { this.create_stream(); // The listeners will not be attached right away, so let's print the deprecation message while the listener is attached this.on('newListener', function (event) { - if (event === 'idle') { - this.warn( - 'The idle event listener is deprecated and will likely be removed in v.3.0.0.\n' + - 'If you rely on this feature please open a new ticket in node_redis with your use case' - ); - } else if (event === 'drain') { + if (event === 'drain') { this.warn( 'The drain event listener is deprecated and will be removed in v.3.0.0.\n' + 'If you want to keep on listening to this event please listen to the stream drain event directly.' @@ -228,7 +223,6 @@ RedisClient.prototype.create_stream = function () { // The buffer_from_socket.toString() has a significant impact on big chunks and therefore this should only be used if necessary debug('Net read ' + self.address + ' id ' + self.connection_id); // + ': ' + buffer_from_socket.toString()); self.reply_parser.execute(buffer_from_socket); - self.emit_idle(); }); this.stream.on('error', function (err) { @@ -623,12 +617,6 @@ RedisClient.prototype.drain = function () { this.should_buffer = false; }; -RedisClient.prototype.emit_idle = function () { - if (this.command_queue.length === 0 && this.pub_sub_mode === 0) { - this.emit('idle'); - } -}; - function normal_reply (self, reply) { var command_obj = self.command_queue.shift(); if (typeof command_obj.callback === 'function') { diff --git a/test/node_redis.spec.js b/test/node_redis.spec.js index e63ae2029b..4ba75c0e82 100644 --- a/test/node_redis.spec.js +++ b/test/node_redis.spec.js @@ -671,22 +671,183 @@ describe('The node_redis client', function () { }); }); - describe('idle', function () { - it('emits idle as soon as there are no outstanding commands', function (done) { + describe('monitor', function () { + it('monitors commands on all redis clients and works in the correct order', function (done) { + var monitorClient = redis.createClient.apply(null, args); + var responses = []; + var end = helper.callFuncAfter(done, 5); + + monitorClient.set('foo', 'bar'); + monitorClient.flushdb(); + monitorClient.monitor(function (err, res) { + assert.strictEqual(res, 'OK'); + client.mget('some', 'keys', 'foo', 'bar'); + client.set('json', JSON.stringify({ + foo: '123', + bar: 'sdflkdfsjk', + another: false + })); + monitorClient.get('baz', function (err, res) { + assert.strictEqual(res, null); + end(err); + }); + monitorClient.set('foo', 'bar" "s are " " good!"', function (err, res) { + assert.strictEqual(res, 'OK'); + end(err); + }); + monitorClient.mget('foo', 'baz', function (err, res) { + assert.strictEqual(res[0], 'bar" "s are " " good!"'); + assert.strictEqual(res[1], null); + end(err); + }); + monitorClient.subscribe('foo', 'baz', function (err, res) { + // The return value might change in v.3 + // assert.strictEqual(res, 'baz'); + // TODO: Fix the return value of subscribe calls + end(err); + }); + }); + + monitorClient.on('monitor', function (time, args, rawOutput) { + assert.strictEqual(monitorClient.monitoring, true); + responses.push(args); + assert(utils.monitor_regex.test(rawOutput), rawOutput); + if (responses.length === 6) { + assert.deepEqual(responses[0], ['mget', 'some', 'keys', 'foo', 'bar']); + assert.deepEqual(responses[1], ['set', 'json', '{"foo":"123","bar":"sdflkdfsjk","another":false}']); + assert.deepEqual(responses[2], ['get', 'baz']); + assert.deepEqual(responses[3], ['set', 'foo', 'bar" "s are " " good!"']); + assert.deepEqual(responses[4], ['mget', 'foo', 'baz']); + assert.deepEqual(responses[5], ['subscribe', 'foo', 'baz']); + monitorClient.quit(end); + } + }); + }); + + it('monitors returns strings in the rawOutput even with return_buffers activated', function (done) { + var monitorClient = redis.createClient({ + return_buffers: true + }); + + monitorClient.MONITOR(function (err, res) { + assert.strictEqual(monitorClient.monitoring, true); + assert.strictEqual(res.inspect(), new Buffer('OK').inspect()); + client.mget('hello', new Buffer('world')); + }); + + monitorClient.on('monitor', function (time, args, rawOutput) { + assert.strictEqual(typeof rawOutput, 'string'); + assert(utils.monitor_regex.test(rawOutput), rawOutput); + assert.deepEqual(args, ['mget', 'hello', 'world']); + // Quit immediatly ends monitoring mode and therefore does not stream back the quit command + monitorClient.quit(done); + }); + }); + + it('monitors reconnects properly and works with the offline queue', function (done) { + var i = 0; + client.MONITOR(helper.isString('OK')); + client.mget('hello', 'world'); + client.on('monitor', function (time, args, rawOutput) { + assert.strictEqual(client.monitoring, true); + assert(utils.monitor_regex.test(rawOutput), rawOutput); + assert.deepEqual(args, ['mget', 'hello', 'world']); + if (i++ === 2) { + // End after two reconnects + return done(); + } + client.stream.destroy(); + client.mget('hello', 'world'); + }); + }); + + it('monitors reconnects properly and works with the offline queue in a batch statement', function (done) { + var i = 0; + var multi = client.batch(); + multi.MONITOR(helper.isString('OK')); + multi.mget('hello', 'world'); + multi.exec(function (err, res) { + assert.deepEqual(res, ['OK', [null, null]]); + }); + client.on('monitor', function (time, args, rawOutput) { + assert.strictEqual(client.monitoring, true); + assert(utils.monitor_regex.test(rawOutput), rawOutput); + assert.deepEqual(args, ['mget', 'hello', 'world']); + if (i++ === 2) { + // End after two reconnects + return done(); + } + client.stream.destroy(); + client.mget('hello', 'world'); + }); + }); + + it('monitor activates even if the command could not be processed properly after a reconnect', function (done) { + client.MONITOR(function (err, res) { + assert.strictEqual(err.code, 'UNCERTAIN_STATE'); + }); + client.on('error', function (err) {}); // Ignore error here + client.stream.destroy(); var end = helper.callFuncAfter(done, 2); - client.on('warning', function (msg) { - assert.strictEqual( - msg, - 'The idle event listener is deprecated and will likely be removed in v.3.0.0.\n' + - 'If you rely on this feature please open a new ticket in node_redis with your use case' - ); + client.on('monitor', function (time, args, rawOutput) { + assert.strictEqual(client.monitoring, true); end(); }); - client.on('idle', function onIdle () { - client.removeListener('idle', onIdle); - client.get('foo', helper.isString('bar', end)); + client.on('reconnecting', function () { + client.get('foo', function (err, res) { + assert(!err); + assert.strictEqual(client.monitoring, true); + end(); + }); + }); + }); + + it('monitors works in combination with the pub sub mode and the offline queue', function (done) { + var responses = []; + var pub = redis.createClient(); + pub.on('ready', function () { + client.MONITOR(function (err, res) { + assert.strictEqual(res, 'OK'); + pub.get('foo', helper.isNull()); + }); + client.subscribe('/foo', '/bar'); + client.unsubscribe('/bar'); + setTimeout(function () { + client.stream.destroy(); + client.once('ready', function () { + pub.publish('/foo', 'hello world'); + }); + client.set('foo', 'bar', helper.isError()); + client.subscribe('baz'); + client.unsubscribe('baz'); + }, 150); + var called = false; + client.on('monitor', function (time, args, rawOutput) { + responses.push(args); + assert(utils.monitor_regex.test(rawOutput), rawOutput); + if (responses.length === 7) { + assert.deepEqual(responses[0], ['subscribe', '/foo', '/bar']); + assert.deepEqual(responses[1], ['unsubscribe', '/bar']); + assert.deepEqual(responses[2], ['get', 'foo']); + assert.deepEqual(responses[3], ['subscribe', '/foo']); + assert.deepEqual(responses[4], ['subscribe', 'baz']); + assert.deepEqual(responses[5], ['unsubscribe', 'baz']); + assert.deepEqual(responses[6], ['publish', '/foo', 'hello world']); + // The publish is called right after the reconnect and the monitor is called before the message is emitted. + // Therefore we have to wait till the next tick + process.nextTick(function () { + assert(called); + client.quit(done); + pub.end(false); + }); + } + }); + client.on('message', function (channel, msg) { + assert.strictEqual(channel, '/foo'); + assert.strictEqual(msg, 'hello world'); + called = true; + }); }); - client.set('foo', 'bar'); }); });