Skip to content

Commit 344291a

Browse files
author
Ruben Bridgewater
committed
Fix monitoring mode
1 parent bf568b6 commit 344291a

File tree

6 files changed

+150
-81
lines changed

6 files changed

+150
-81
lines changed

README.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -545,22 +545,20 @@ If you fire many commands at once this is going to **boost the execution speed b
545545
Redis supports the `MONITOR` command, which lets you see all commands received by the Redis server
546546
across all client connections, including from other client libraries and other computers.
547547

548-
After you send the `MONITOR` command, no other commands are valid on that connection. `node_redis`
549-
will emit a `monitor` event for every new monitor message that comes across. The callback for the
550-
`monitor` event takes a timestamp from the Redis server and an array of command arguments.
548+
A `monitor` event is going to be emitted for every command fired from any client connected to the server including the monitoring client itself.
549+
The callback for the `monitor` event takes a timestamp from the Redis server, an array of command arguments and the raw monitoring string.
551550

552551
Here is a simple example:
553552

554553
```js
555-
var client = require("redis").createClient(),
556-
util = require("util");
557-
554+
var client = require("redis").createClient();
558555
client.monitor(function (err, res) {
559556
console.log("Entering monitoring mode.");
560557
});
558+
client.set('foo', 'bar');
561559

562-
client.on("monitor", function (time, args) {
563-
console.log(time + ": " + util.inspect(args));
560+
client.on("monitor", function (time, args, raw_reply) {
561+
console.log(time + ": " + args); // 1458910076.446514:['set', 'foo', 'bar']
564562
});
565563
```
566564

changelog.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,20 @@
11
Changelog
22
=========
33

4+
## v.2.6.0 - XX Mar, 2016
5+
6+
Features
7+
8+
- Monitor now works together with the offline queue
9+
- All commands that were send after a connection loss are now going to be send after reconnecting
10+
- Activating monitor mode does now work together with arbitrary commands including pub sub mode
11+
12+
Bugfixes
13+
14+
- Fixed calling monitor command while other commands are still running
15+
- Fixed monitor and pub sub mode not working together
16+
- Fixed monitor mode not working in combination with the offline queue
17+
418
## v.2.5.3 - 21 Mar, 2016
519

620
Bugfixes

index.js

Lines changed: 22 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ function RedisClient (options, stream) {
140140
this.pipeline = 0;
141141
this.times_connected = 0;
142142
this.options = options;
143+
this.buffers = options.return_buffers || options.detect_buffers;
143144
// Init parser
144145
this.reply_parser = Parser({
145146
returnReply: function (data) {
@@ -154,7 +155,7 @@ function RedisClient (options, stream) {
154155
self.stream.destroy();
155156
self.return_error(err);
156157
},
157-
returnBuffers: options.return_buffers || options.detect_buffers,
158+
returnBuffers: this.buffers,
158159
name: options.parser
159160
});
160161
this.create_stream();
@@ -329,9 +330,7 @@ RedisClient.prototype.on_error = function (err) {
329330
}
330331

331332
err.message = 'Redis connection to ' + this.address + ' failed - ' + err.message;
332-
333333
debug(err.message);
334-
335334
this.connected = false;
336335
this.ready = false;
337336

@@ -369,12 +368,6 @@ RedisClient.prototype.on_ready = function () {
369368
debug('on_ready called ' + this.address + ' id ' + this.connection_id);
370369
this.ready = true;
371370

372-
if (this.old_state !== null) {
373-
this.monitoring = this.old_state.monitoring;
374-
this.pub_sub_mode = this.old_state.pub_sub_mode;
375-
this.old_state = null;
376-
}
377-
378371
var cork;
379372
if (!this.stream.cork) {
380373
cork = function (len) {
@@ -393,16 +386,15 @@ RedisClient.prototype.on_ready = function () {
393386
}
394387
this.cork = cork;
395388

396-
// restore modal commands from previous connection
389+
// restore modal commands from previous connection. The order of the commands is important
397390
if (this.selected_db !== undefined) {
398-
// this trick works if and only if the following send_command
399-
// never goes into the offline queue
400-
var pub_sub_mode = this.pub_sub_mode;
401-
this.pub_sub_mode = false;
402391
this.send_command('select', [this.selected_db]);
403-
this.pub_sub_mode = pub_sub_mode;
404392
}
405-
if (this.pub_sub_mode === true) {
393+
if (this.old_state !== null) {
394+
this.monitoring = this.old_state.monitoring;
395+
this.pub_sub_mode = this.old_state.pub_sub_mode;
396+
}
397+
if (this.pub_sub_mode) {
406398
// only emit 'ready' when all subscriptions were made again
407399
var callback_count = 0;
408400
var callback = function () {
@@ -424,12 +416,10 @@ RedisClient.prototype.on_ready = function () {
424416
});
425417
return;
426418
}
427-
428419
if (this.monitoring) {
429420
this.send_command('monitor', []);
430-
} else {
431-
this.send_offline_queue();
432421
}
422+
this.send_offline_queue();
433423
this.emit('ready');
434424
};
435425

@@ -525,15 +515,13 @@ RedisClient.prototype.connection_gone = function (why, error) {
525515
this.cork = noop;
526516
this.pipeline = 0;
527517

528-
if (this.old_state === null) {
529-
var state = {
530-
monitoring: this.monitoring,
531-
pub_sub_mode: this.pub_sub_mode
532-
};
533-
this.old_state = state;
534-
this.monitoring = false;
535-
this.pub_sub_mode = false;
536-
}
518+
var state = {
519+
monitoring: this.monitoring,
520+
pub_sub_mode: this.pub_sub_mode
521+
};
522+
this.old_state = state;
523+
this.monitoring = false;
524+
this.pub_sub_mode = false;
537525

538526
// since we are collapsing end and close, users don't expect to be called twice
539527
if (!this.emitted_end) {
@@ -604,9 +592,7 @@ RedisClient.prototype.connection_gone = function (why, error) {
604592
};
605593

606594
RedisClient.prototype.return_error = function (err) {
607-
var command_obj = this.command_queue.shift(),
608-
queue_len = this.command_queue.length;
609-
595+
var command_obj = this.command_queue.shift();
610596
if (command_obj && command_obj.command && command_obj.command.toUpperCase) {
611597
err.command = command_obj.command.toUpperCase();
612598
}
@@ -617,8 +603,7 @@ RedisClient.prototype.return_error = function (err) {
617603
err.code = match[1];
618604
}
619605

620-
this.emit_idle(queue_len);
621-
606+
this.emit_idle();
622607
utils.callback_or_emit(this, command_obj && command_obj.callback, err);
623608
};
624609

@@ -627,8 +612,8 @@ RedisClient.prototype.drain = function () {
627612
this.should_buffer = false;
628613
};
629614

630-
RedisClient.prototype.emit_idle = function (queue_len) {
631-
if (queue_len === 0 && this.pub_sub_mode === false) {
615+
RedisClient.prototype.emit_idle = function () {
616+
if (this.command_queue.length === 0 && this.pub_sub_mode === false) {
632617
this.emit('idle');
633618
}
634619
};
@@ -640,20 +625,6 @@ function queue_state_error (self, command_obj) {
640625
self.emit('error', err);
641626
}
642627

643-
function monitor (self, reply) {
644-
if (typeof reply !== 'string') {
645-
reply = reply.toString();
646-
}
647-
// If in monitoring mode only two commands are valid ones: AUTH and MONITOR wich reply with OK
648-
var len = reply.indexOf(' ');
649-
var timestamp = reply.slice(0, len);
650-
var argindex = reply.indexOf('"');
651-
var args = reply.slice(argindex + 1, -1).split('" "').map(function (elem) {
652-
return elem.replace(/\\"/g, '"');
653-
});
654-
self.emit('monitor', timestamp, args);
655-
}
656-
657628
function normal_reply (self, reply, command_obj) {
658629
if (typeof command_obj.callback === 'function') {
659630
if ('exec' !== command_obj.command) {
@@ -716,17 +687,15 @@ RedisClient.prototype.return_reply = function (reply) {
716687

717688
queue_len = this.command_queue.length;
718689

719-
this.emit_idle(queue_len);
690+
this.emit_idle();
720691

721692
if (command_obj && !command_obj.sub_command) {
722693
normal_reply(this, reply, command_obj);
723694
} else if (this.pub_sub_mode || command_obj && command_obj.sub_command) {
724695
return_pub_sub(this, reply, command_obj);
725696
}
726697
/* istanbul ignore else: this is a safety check that we should not be able to trigger */
727-
else if (this.monitoring) {
728-
monitor(this, reply);
729-
} else {
698+
else if (!this.monitoring) {
730699
queue_state_error(this, command_obj);
731700
}
732701
};
@@ -837,8 +806,6 @@ RedisClient.prototype.send_command = function (command, args, callback) {
837806

838807
if (command === 'subscribe' || command === 'psubscribe' || command === 'unsubscribe' || command === 'punsubscribe') {
839808
this.pub_sub_command(command_obj); // TODO: This has to be moved to the result handler
840-
} else if (command === 'monitor') {
841-
this.monitoring = true;
842809
} else if (command === 'quit') {
843810
this.closing = true;
844811
}

lib/individualCommands.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,41 @@ RedisClient.prototype.select = RedisClient.prototype.SELECT = function select (d
3333
});
3434
};
3535

36+
RedisClient.prototype.monitor = RedisClient.prototype.MONITOR = function (callback) {
37+
// Use a individual command, as this is a special case that does not has to be checked for any other command
38+
var self = this;
39+
return this.send_command('monitor', [], function (err, res) {
40+
if (err === null) {
41+
self.reply_parser.returnReply = function (reply) {
42+
// If in monitor mode, all normal commands are still working and we only want to emit the streamlined commands
43+
// As this is not the average use case and monitor is expensive anyway, let's change the code here, to improve
44+
// the average performance of all other commands in case of no monitor mode
45+
if (self.monitoring) {
46+
var replyStr;
47+
if (self.buffers && Buffer.isBuffer(reply)) {
48+
replyStr = reply.toString();
49+
} else {
50+
replyStr = reply;
51+
}
52+
// While reconnecting the redis server does not recognize the client as in monitor mode anymore
53+
// Therefor the monitor command has to finish before it catches further commands
54+
if (typeof replyStr === 'string' && utils.monitor_regex.test(replyStr)) {
55+
var timestamp = replyStr.slice(0, replyStr.indexOf(' '));
56+
var args = replyStr.slice(replyStr.indexOf('"') + 1, -1).split('" "').map(function (elem) {
57+
return elem.replace(/\\"/g, '"');
58+
});
59+
self.emit('monitor', timestamp, args, replyStr);
60+
return;
61+
}
62+
}
63+
self.return_reply(reply);
64+
};
65+
self.monitoring = true;
66+
}
67+
utils.callback_or_emit(self, callback, err, res);
68+
});
69+
};
70+
3671
// Store info in this.server_info after each call
3772
RedisClient.prototype.info = RedisClient.prototype.INFO = function info (section, callback) {
3873
var self = this;

lib/utils.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,6 @@ function print (err, reply) {
3939
}
4040
}
4141

42-
var redisErrCode = /^([A-Z]+)\s+(.+)$/;
43-
4442
// Deep clone arbitrary objects with arrays. Can't handle cyclic structures (results in a range error)
4543
// Any attribute with a non primitive value besides object and array will be passed by reference (e.g. Buffers, Maps, Functions)
4644
function clone (obj) {
@@ -102,7 +100,8 @@ module.exports = {
102100
reply_to_strings: replyToStrings,
103101
reply_to_object: replyToObject,
104102
print: print,
105-
err_code: redisErrCode,
103+
err_code: /^([A-Z]+)\s+(.+)$/,
104+
monitor_regex: /^[0-9]{10,11}\.[0-9]+ \[[0-9]{1,3} [0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]{1,5}\].*/,
106105
clone: convenienceClone,
107106
callback_or_emit: callbackOrEmit,
108107
reply_in_order: replyInOrder

0 commit comments

Comments
 (0)