You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-10 11:43:01 +03:00
Merge pull request #1017 from NodeRedis/pubsub
Fix pub sub mode Fixes #603 Fixes #577 Fixes #137
This commit is contained in:
38
README.md
38
README.md
@@ -233,7 +233,7 @@ client.get("foo_rand000000000000", function (err, reply) {
|
|||||||
client.get(new Buffer("foo_rand000000000000"), function (err, reply) {
|
client.get(new Buffer("foo_rand000000000000"), function (err, reply) {
|
||||||
console.log(reply.toString()); // Will print `<Buffer 4f 4b>`
|
console.log(reply.toString()); // Will print `<Buffer 4f 4b>`
|
||||||
});
|
});
|
||||||
client.end();
|
client.quit();
|
||||||
```
|
```
|
||||||
|
|
||||||
retry_strategy example
|
retry_strategy example
|
||||||
@@ -302,7 +302,7 @@ client.get("foo_rand000000000000", function (err, reply) {
|
|||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
`client.end()` without the flush parameter should NOT be used in production!
|
`client.end()` without the flush parameter set to true should NOT be used in production!
|
||||||
|
|
||||||
## client.unref()
|
## client.unref()
|
||||||
|
|
||||||
@@ -377,34 +377,34 @@ client connections, subscribes to a channel on one of them, and publishes to tha
|
|||||||
channel on the other:
|
channel on the other:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var redis = require("redis"),
|
var redis = require("redis");
|
||||||
client1 = redis.createClient(), client2 = redis.createClient(),
|
var sub = redis.createClient(), pub = redis.createClient();
|
||||||
msg_count = 0;
|
var msg_count = 0;
|
||||||
|
|
||||||
client1.on("subscribe", function (channel, count) {
|
sub.on("subscribe", function (channel, count) {
|
||||||
client2.publish("a nice channel", "I am sending a message.");
|
pub.publish("a nice channel", "I am sending a message.");
|
||||||
client2.publish("a nice channel", "I am sending a second message.");
|
pub.publish("a nice channel", "I am sending a second message.");
|
||||||
client2.publish("a nice channel", "I am sending my last message.");
|
pub.publish("a nice channel", "I am sending my last message.");
|
||||||
});
|
});
|
||||||
|
|
||||||
client1.on("message", function (channel, message) {
|
sub.on("message", function (channel, message) {
|
||||||
console.log("client1 channel " + channel + ": " + message);
|
console.log("sub channel " + channel + ": " + message);
|
||||||
msg_count += 1;
|
msg_count += 1;
|
||||||
if (msg_count === 3) {
|
if (msg_count === 3) {
|
||||||
client1.unsubscribe();
|
sub.unsubscribe();
|
||||||
client1.end();
|
sub.quit();
|
||||||
client2.end();
|
pub.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
client1.subscribe("a nice channel");
|
sub.subscribe("a nice channel");
|
||||||
```
|
```
|
||||||
|
|
||||||
When a client issues a `SUBSCRIBE` or `PSUBSCRIBE`, that connection is put into a "subscriber" mode.
|
When a client issues a `SUBSCRIBE` or `PSUBSCRIBE`, that connection is put into a "subscriber" mode.
|
||||||
At that point, only commands that modify the subscription set are valid. When the subscription
|
At that point, only commands that modify the subscription set are valid and quit (and depending on the redis version ping as well). When the subscription
|
||||||
set is empty, the connection is put back into regular mode.
|
set is empty, the connection is put back into regular mode.
|
||||||
|
|
||||||
If you need to send regular commands to Redis while in subscriber mode, just open another connection.
|
If you need to send regular commands to Redis while in subscriber mode, just open another connection with a new client (hint: use `client.duplicate()`).
|
||||||
|
|
||||||
## Subscriber Events
|
## Subscriber Events
|
||||||
|
|
||||||
@@ -413,13 +413,13 @@ If a client has subscriptions active, it may emit these events:
|
|||||||
### "message" (channel, message)
|
### "message" (channel, message)
|
||||||
|
|
||||||
Client will emit `message` for every message received that matches an active subscription.
|
Client will emit `message` for every message received that matches an active subscription.
|
||||||
Listeners are passed the channel name as `channel` and the message Buffer as `message`.
|
Listeners are passed the channel name as `channel` and the message as `message`.
|
||||||
|
|
||||||
### "pmessage" (pattern, channel, message)
|
### "pmessage" (pattern, channel, message)
|
||||||
|
|
||||||
Client will emit `pmessage` for every message received that matches an active subscription pattern.
|
Client will emit `pmessage` for every message received that matches an active subscription pattern.
|
||||||
Listeners are passed the original pattern used with `PSUBSCRIBE` as `pattern`, the sending channel
|
Listeners are passed the original pattern used with `PSUBSCRIBE` as `pattern`, the sending channel
|
||||||
name as `channel`, and the message Buffer as `message`.
|
name as `channel`, and the message as `message`.
|
||||||
|
|
||||||
### "subscribe" (channel, count)
|
### "subscribe" (channel, count)
|
||||||
|
|
||||||
|
@@ -5,15 +5,21 @@ Changelog
|
|||||||
|
|
||||||
Features
|
Features
|
||||||
|
|
||||||
- Monitor now works together with the offline queue
|
- Monitor and pub sub mode now work together with the offline queue
|
||||||
- All commands that were send after a connection loss are now going to be send after reconnecting
|
- All commands that were send after a connection loss are now going to be send after reconnecting
|
||||||
- Activating monitor mode does now work together with arbitrary commands including pub sub mode
|
- Activating monitor mode does now work together with arbitrary commands including pub sub mode
|
||||||
|
- Pub sub mode is completly rewritten and all known issues fixed
|
||||||
|
|
||||||
Bugfixes
|
Bugfixes
|
||||||
|
|
||||||
- Fixed calling monitor command while other commands are still running
|
- Fixed calling monitor command while other commands are still running
|
||||||
- Fixed monitor and pub sub mode not working together
|
- Fixed monitor and pub sub mode not working together
|
||||||
- Fixed monitor mode not working in combination with the offline queue
|
- Fixed monitor mode not working in combination with the offline queue
|
||||||
|
- Fixed pub sub mode not working in combination with the offline queue
|
||||||
|
- Fixed pub sub mode resubscribing not working with non utf8 buffer channels
|
||||||
|
- Fixed pub sub mode crashing if calling unsubscribe / subscribe in various combinations
|
||||||
|
- Fixed pub sub mode emitting unsubscribe even if no channels were unsubscribed
|
||||||
|
- Fixed pub sub mode emitting a message without a message published
|
||||||
|
|
||||||
## v.2.5.3 - 21 Mar, 2016
|
## v.2.5.3 - 21 Mar, 2016
|
||||||
|
|
||||||
|
244
index.js
244
index.js
@@ -5,7 +5,8 @@ var tls = require('tls');
|
|||||||
var util = require('util');
|
var util = require('util');
|
||||||
var utils = require('./lib/utils');
|
var utils = require('./lib/utils');
|
||||||
var Queue = require('double-ended-queue');
|
var Queue = require('double-ended-queue');
|
||||||
var Command = require('./lib/command');
|
var Command = require('./lib/command').Command;
|
||||||
|
var OfflineCommand = require('./lib/command').OfflineCommand;
|
||||||
var EventEmitter = require('events');
|
var EventEmitter = require('events');
|
||||||
var Parser = require('redis-parser');
|
var Parser = require('redis-parser');
|
||||||
var commands = require('redis-commands');
|
var commands = require('redis-commands');
|
||||||
@@ -128,7 +129,7 @@ function RedisClient (options, stream) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.initialize_retry_vars();
|
this.initialize_retry_vars();
|
||||||
this.pub_sub_mode = false;
|
this.pub_sub_mode = 0;
|
||||||
this.subscription_set = {};
|
this.subscription_set = {};
|
||||||
this.monitoring = false;
|
this.monitoring = false;
|
||||||
this.closing = false;
|
this.closing = false;
|
||||||
@@ -222,6 +223,7 @@ RedisClient.prototype.create_stream = function () {
|
|||||||
// The buffer_from_socket.toString() has a significant impact on big chunks and therefor this should only be used if necessary
|
// The buffer_from_socket.toString() has a significant impact on big chunks and therefor this should only be used if necessary
|
||||||
debug('Net read ' + self.address + ' id ' + self.connection_id); // + ': ' + buffer_from_socket.toString());
|
debug('Net read ' + self.address + ' id ' + self.connection_id); // + ': ' + buffer_from_socket.toString());
|
||||||
self.reply_parser.execute(buffer_from_socket);
|
self.reply_parser.execute(buffer_from_socket);
|
||||||
|
self.emit_idle();
|
||||||
});
|
});
|
||||||
|
|
||||||
this.stream.on('error', function (err) {
|
this.stream.on('error', function (err) {
|
||||||
@@ -386,7 +388,7 @@ RedisClient.prototype.on_ready = function () {
|
|||||||
}
|
}
|
||||||
this.cork = cork;
|
this.cork = cork;
|
||||||
|
|
||||||
// restore modal commands from previous connection. The order of the commands is important
|
// Restore modal commands from previous connection. The order of the commands is important
|
||||||
if (this.selected_db !== undefined) {
|
if (this.selected_db !== undefined) {
|
||||||
this.send_command('select', [this.selected_db]);
|
this.send_command('select', [this.selected_db]);
|
||||||
}
|
}
|
||||||
@@ -394,31 +396,29 @@ RedisClient.prototype.on_ready = function () {
|
|||||||
this.monitoring = this.old_state.monitoring;
|
this.monitoring = this.old_state.monitoring;
|
||||||
this.pub_sub_mode = this.old_state.pub_sub_mode;
|
this.pub_sub_mode = this.old_state.pub_sub_mode;
|
||||||
}
|
}
|
||||||
if (this.pub_sub_mode) {
|
if (this.monitoring) { // Monitor has to be fired before pub sub commands
|
||||||
|
this.send_command('monitor', []);
|
||||||
|
}
|
||||||
|
var callback_count = Object.keys(this.subscription_set).length;
|
||||||
|
if (!this.options.disable_resubscribing && callback_count) {
|
||||||
// only emit 'ready' when all subscriptions were made again
|
// only emit 'ready' when all subscriptions were made again
|
||||||
var callback_count = 0;
|
// TODO: Remove the countdown for ready here. This is not coherent with all other modes and should therefor not be handled special
|
||||||
|
// We know we are ready as soon as all commands were fired
|
||||||
var callback = function () {
|
var callback = function () {
|
||||||
callback_count--;
|
callback_count--;
|
||||||
if (callback_count === 0) {
|
if (callback_count === 0) {
|
||||||
self.emit('ready');
|
self.emit('ready');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (this.options.disable_resubscribing) {
|
debug('Sending pub/sub on_ready commands');
|
||||||
this.emit('ready');
|
for (var key in this.subscription_set) { // jshint ignore: line
|
||||||
return;
|
var command = key.slice(0, key.indexOf('_'));
|
||||||
|
var args = self.subscription_set[key];
|
||||||
|
self.send_command(command, [args], callback);
|
||||||
}
|
}
|
||||||
Object.keys(this.subscription_set).forEach(function (key) {
|
this.send_offline_queue();
|
||||||
var space_index = key.indexOf(' ');
|
|
||||||
var parts = [key.slice(0, space_index), key.slice(space_index + 1)];
|
|
||||||
debug('Sending pub/sub on_ready ' + parts[0] + ', ' + parts[1]);
|
|
||||||
callback_count++;
|
|
||||||
self.send_command(parts[0] + 'scribe', [parts[1]], callback);
|
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.monitoring) {
|
|
||||||
this.send_command('monitor', []);
|
|
||||||
}
|
|
||||||
this.send_offline_queue();
|
this.send_offline_queue();
|
||||||
this.emit('ready');
|
this.emit('ready');
|
||||||
};
|
};
|
||||||
@@ -521,7 +521,7 @@ RedisClient.prototype.connection_gone = function (why, error) {
|
|||||||
};
|
};
|
||||||
this.old_state = state;
|
this.old_state = state;
|
||||||
this.monitoring = false;
|
this.monitoring = false;
|
||||||
this.pub_sub_mode = false;
|
this.pub_sub_mode = 0;
|
||||||
|
|
||||||
// since we are collapsing end and close, users don't expect to be called twice
|
// since we are collapsing end and close, users don't expect to be called twice
|
||||||
if (!this.emitted_end) {
|
if (!this.emitted_end) {
|
||||||
@@ -603,7 +603,6 @@ RedisClient.prototype.return_error = function (err) {
|
|||||||
err.code = match[1];
|
err.code = match[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit_idle();
|
|
||||||
utils.callback_or_emit(this, command_obj && command_obj.callback, err);
|
utils.callback_or_emit(this, command_obj && command_obj.callback, err);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -613,19 +612,13 @@ RedisClient.prototype.drain = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
RedisClient.prototype.emit_idle = function () {
|
RedisClient.prototype.emit_idle = function () {
|
||||||
if (this.command_queue.length === 0 && this.pub_sub_mode === false) {
|
if (this.command_queue.length === 0 && this.pub_sub_mode === 0) {
|
||||||
this.emit('idle');
|
this.emit('idle');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/* istanbul ignore next: this is a safety check that we should not be able to trigger */
|
function normal_reply (self, reply) {
|
||||||
function queue_state_error (self, command_obj) {
|
var command_obj = self.command_queue.shift();
|
||||||
var err = new Error('node_redis command queue state error. If you can reproduce this, please report it.');
|
|
||||||
err.command_obj = command_obj;
|
|
||||||
self.emit('error', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
function normal_reply (self, reply, command_obj) {
|
|
||||||
if (typeof command_obj.callback === 'function') {
|
if (typeof command_obj.callback === 'function') {
|
||||||
if ('exec' !== command_obj.command) {
|
if ('exec' !== command_obj.command) {
|
||||||
reply = self.handle_reply(reply, command_obj.command, command_obj.buffer_args);
|
reply = self.handle_reply(reply, command_obj.command, command_obj.buffer_args);
|
||||||
@@ -636,67 +629,107 @@ function normal_reply (self, reply, command_obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function return_pub_sub (self, reply, command_obj) {
|
function set_subscribe (self, type, command_obj, subscribe, reply) {
|
||||||
if (reply instanceof Array) {
|
var i = 0;
|
||||||
if ((!command_obj || command_obj.buffer_args === false) && !self.options.return_buffers) {
|
if (subscribe) {
|
||||||
reply = utils.reply_to_strings(reply);
|
// The channels have to be saved one after the other and the type has to be the same too,
|
||||||
|
// to make sure partly subscribe / unsubscribe works well together
|
||||||
|
for (; i < command_obj.args.length; i++) {
|
||||||
|
self.subscription_set[type + '_' + command_obj.args[i]] = command_obj.args[i];
|
||||||
}
|
}
|
||||||
var type = reply[0].toString();
|
} else {
|
||||||
|
type = type === 'unsubscribe' ? 'subscribe' : 'psubscribe'; // Make types consistent
|
||||||
|
for (; i < command_obj.args.length; i++) {
|
||||||
|
delete self.subscription_set[type + '_' + command_obj.args[i]];
|
||||||
|
}
|
||||||
|
if (reply[2] === 0) { // No channels left that this client is subscribed to
|
||||||
|
var running_command;
|
||||||
|
i = 0;
|
||||||
|
// This should be a rare case and therefor handling it this way should be good performance wise for the general case
|
||||||
|
while (running_command = self.command_queue.get(i++)) {
|
||||||
|
if (
|
||||||
|
running_command.command === 'subscribe' ||
|
||||||
|
running_command.command === 'psubscribe' ||
|
||||||
|
running_command.command === 'unsubscribe' ||
|
||||||
|
running_command.command === 'punsubscribe'
|
||||||
|
) {
|
||||||
|
self.pub_sub_mode = i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.pub_sub_mode = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add buffer emiters (we have to get all pubsub messages as buffers back in that case)
|
function subscribe_unsubscribe (self, reply, type, subscribe) {
|
||||||
if (type === 'message') {
|
// Subscribe commands take an optional callback and also emit an event, but only the _last_ response is included in the callback
|
||||||
self.emit('message', reply[1], reply[2]); // channcel, message
|
var command_obj = self.command_queue.get(0);
|
||||||
} else if (type === 'pmessage') {
|
var buffer = self.options.return_buffers || self.options.detect_buffers && command_obj && command_obj.buffer_args || reply[1] === null;
|
||||||
self.emit('pmessage', reply[1], reply[2], reply[3]); // pattern, channcel, message
|
var channel = buffer ? reply[1] : reply[1].toString();
|
||||||
} else if (type === 'subscribe' || type === 'unsubscribe' || type === 'psubscribe' || type === 'punsubscribe') {
|
var count = reply[2];
|
||||||
if (reply[2].toString() === '0') {
|
debug('Subscribe / unsubscribe command');
|
||||||
self.pub_sub_mode = false;
|
|
||||||
debug('All subscriptions removed, exiting pub/sub mode');
|
// Emit first, then return the callback
|
||||||
} else {
|
if (channel !== null) { // Do not emit something if there was no channel to unsubscribe from
|
||||||
self.pub_sub_mode = true;
|
self.emit(type, channel, count);
|
||||||
}
|
}
|
||||||
// Subscribe commands take an optional callback and also emit an event, but only the first response is included in the callback
|
// The pub sub commands return each argument in a separate return value and have to be handled that way
|
||||||
// TODO - document this or fix it so it works in a more obvious way
|
if (command_obj.sub_commands_left <= 1) {
|
||||||
if (command_obj && typeof command_obj.callback === 'function') {
|
if (count !== 0 && !subscribe && command_obj.args.length === 0) {
|
||||||
command_obj.callback(null, reply[1]);
|
command_obj.sub_commands_left = count;
|
||||||
}
|
return;
|
||||||
self.emit(type, reply[1], reply[2]); // channcel, count
|
|
||||||
} else {
|
|
||||||
self.emit('error', new Error('subscriptions are active but got unknown reply type ' + type));
|
|
||||||
}
|
}
|
||||||
} else if (!self.closing) {
|
self.command_queue.shift();
|
||||||
self.emit('error', new Error('subscriptions are active but got an invalid reply: ' + reply));
|
set_subscribe(self, type, command_obj, subscribe, reply);
|
||||||
|
if (typeof command_obj.callback === 'function') {
|
||||||
|
// TODO: The current return value is pretty useless.
|
||||||
|
// Evaluate to change this in v.3 to return all subscribed / unsubscribed channels in an array including the number of channels subscribed too
|
||||||
|
command_obj.callback(null, channel);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
command_obj.sub_commands_left--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function return_pub_sub (self, reply) {
|
||||||
|
var type = reply[0].toString();
|
||||||
|
if (type === 'message') { // channel, message
|
||||||
|
// TODO: Implement message_buffer
|
||||||
|
// if (self.buffers) {
|
||||||
|
// self.emit('message_buffer', reply[1], reply[2]);
|
||||||
|
// }
|
||||||
|
if (!self.options.return_buffers) { // backwards compatible. Refactor this in v.3 to always return a string on the normal emitter
|
||||||
|
self.emit('message', reply[1].toString(), reply[2].toString());
|
||||||
|
} else {
|
||||||
|
self.emit('message', reply[1], reply[2]);
|
||||||
|
}
|
||||||
|
} else if (type === 'pmessage') { // pattern, channel, message
|
||||||
|
// if (self.buffers) {
|
||||||
|
// self.emit('pmessage_buffer', reply[1], reply[2], reply[3]);
|
||||||
|
// }
|
||||||
|
if (!self.options.return_buffers) { // backwards compatible. Refactor this in v.3 to always return a string on the normal emitter
|
||||||
|
self.emit('pmessage', reply[1].toString(), reply[2].toString(), reply[3].toString());
|
||||||
|
} else {
|
||||||
|
self.emit('pmessage', reply[1], reply[2], reply[3]);
|
||||||
|
}
|
||||||
|
} else if (type === 'subscribe' || type === 'psubscribe') {
|
||||||
|
subscribe_unsubscribe(self, reply, type, true);
|
||||||
|
} else if (type === 'unsubscribe' || type === 'punsubscribe') {
|
||||||
|
subscribe_unsubscribe(self, reply, type, false);
|
||||||
|
} else {
|
||||||
|
normal_reply(self, reply);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
RedisClient.prototype.return_reply = function (reply) {
|
RedisClient.prototype.return_reply = function (reply) {
|
||||||
var command_obj, type, queue_len;
|
if (this.pub_sub_mode === 1 && reply instanceof Array && reply.length !== 0 && reply[0]) {
|
||||||
|
return_pub_sub(this, reply);
|
||||||
// If the 'reply' here is actually a message received asynchronously due to a
|
|
||||||
// pubsub subscription, don't pop the command queue as we'll only be consuming
|
|
||||||
// the head command prematurely.
|
|
||||||
if (this.pub_sub_mode && reply instanceof Array && reply[0]) {
|
|
||||||
type = reply[0].toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.pub_sub_mode && (type === 'message' || type === 'pmessage')) {
|
|
||||||
debug('Received pubsub message');
|
|
||||||
} else {
|
} else {
|
||||||
command_obj = this.command_queue.shift();
|
if (this.pub_sub_mode !== 0 && this.pub_sub_mode !== 1) {
|
||||||
}
|
this.pub_sub_mode--;
|
||||||
|
}
|
||||||
queue_len = this.command_queue.length;
|
normal_reply(this, reply);
|
||||||
|
|
||||||
this.emit_idle();
|
|
||||||
|
|
||||||
if (command_obj && !command_obj.sub_command) {
|
|
||||||
normal_reply(this, reply, command_obj);
|
|
||||||
} else if (this.pub_sub_mode || command_obj && command_obj.sub_command) {
|
|
||||||
return_pub_sub(this, reply, command_obj);
|
|
||||||
}
|
|
||||||
/* istanbul ignore else: this is a safety check that we should not be able to trigger */
|
|
||||||
else if (!this.monitoring) {
|
|
||||||
queue_state_error(this, command_obj);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -731,16 +764,15 @@ RedisClient.prototype.send_command = function (command, args, callback) {
|
|||||||
var command_str = '';
|
var command_str = '';
|
||||||
var len = 0;
|
var len = 0;
|
||||||
var big_data = false;
|
var big_data = false;
|
||||||
|
var buffer_args = false;
|
||||||
|
|
||||||
if (process.domain && callback) {
|
if (process.domain && callback) {
|
||||||
callback = process.domain.bind(callback);
|
callback = process.domain.bind(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
var command_obj = new Command(command, args, callback);
|
|
||||||
|
|
||||||
if (this.ready === false || this.stream.writable === false) {
|
if (this.ready === false || this.stream.writable === false) {
|
||||||
// Handle offline commands right away
|
// Handle offline commands right away
|
||||||
handle_offline_command(this, command_obj);
|
handle_offline_command(this, new OfflineCommand(command, args, callback));
|
||||||
return false; // Indicate buffering
|
return false; // Indicate buffering
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,7 +808,7 @@ RedisClient.prototype.send_command = function (command, args, callback) {
|
|||||||
args_copy[i] = 'null'; // Backwards compatible :/
|
args_copy[i] = 'null'; // Backwards compatible :/
|
||||||
} else if (Buffer.isBuffer(args[i])) {
|
} else if (Buffer.isBuffer(args[i])) {
|
||||||
args_copy[i] = args[i];
|
args_copy[i] = args[i];
|
||||||
command_obj.buffer_args = true;
|
buffer_args = true;
|
||||||
big_data = true;
|
big_data = true;
|
||||||
if (this.pipeline !== 0) {
|
if (this.pipeline !== 0) {
|
||||||
this.pipeline += 2;
|
this.pipeline += 2;
|
||||||
@@ -803,9 +835,15 @@ RedisClient.prototype.send_command = function (command, args, callback) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
args = null;
|
args = null;
|
||||||
|
var command_obj = new Command(command, args_copy, callback);
|
||||||
|
command_obj.buffer_args = buffer_args;
|
||||||
|
|
||||||
if (command === 'subscribe' || command === 'psubscribe' || command === 'unsubscribe' || command === 'punsubscribe') {
|
if (command === 'subscribe' || command === 'psubscribe' || command === 'unsubscribe' || command === 'punsubscribe') {
|
||||||
this.pub_sub_command(command_obj); // TODO: This has to be moved to the result handler
|
// If pub sub is already activated, keep it that way, otherwise set the number of commands to resolve until pub sub mode activates
|
||||||
|
// Deactivation of the pub sub mode happens in the result handler
|
||||||
|
if (!this.pub_sub_mode) {
|
||||||
|
this.pub_sub_mode = this.command_queue.length + 1;
|
||||||
|
}
|
||||||
} else if (command === 'quit') {
|
} else if (command === 'quit') {
|
||||||
this.closing = true;
|
this.closing = true;
|
||||||
}
|
}
|
||||||
@@ -886,38 +924,6 @@ RedisClient.prototype.write = function (data) {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
RedisClient.prototype.pub_sub_command = function (command_obj) {
|
|
||||||
var i, key, command, args;
|
|
||||||
|
|
||||||
if (this.pub_sub_mode === false) {
|
|
||||||
debug('Entering pub/sub mode from ' + command_obj.command);
|
|
||||||
}
|
|
||||||
this.pub_sub_mode = true;
|
|
||||||
command_obj.sub_command = true;
|
|
||||||
|
|
||||||
command = command_obj.command;
|
|
||||||
args = command_obj.args;
|
|
||||||
if (command === 'subscribe' || command === 'psubscribe') {
|
|
||||||
if (command === 'subscribe') {
|
|
||||||
key = 'sub';
|
|
||||||
} else {
|
|
||||||
key = 'psub';
|
|
||||||
}
|
|
||||||
for (i = 0; i < args.length; i++) {
|
|
||||||
this.subscription_set[key + ' ' + args[i]] = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (command === 'unsubscribe') {
|
|
||||||
key = 'sub';
|
|
||||||
} else {
|
|
||||||
key = 'psub';
|
|
||||||
}
|
|
||||||
for (i = 0; i < args.length; i++) {
|
|
||||||
delete this.subscription_set[key + ' ' + args[i]];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
RedisClient.prototype.end = function (flush) {
|
RedisClient.prototype.end = function (flush) {
|
||||||
// Flush queue if wanted
|
// Flush queue if wanted
|
||||||
if (flush) {
|
if (flush) {
|
||||||
|
@@ -4,9 +4,19 @@
|
|||||||
// a named constructor helps it show up meaningfully in the V8 CPU profiler and in heap snapshots.
|
// a named constructor helps it show up meaningfully in the V8 CPU profiler and in heap snapshots.
|
||||||
function Command(command, args, callback) {
|
function Command(command, args, callback) {
|
||||||
this.command = command;
|
this.command = command;
|
||||||
this.args = args;
|
this.args = args; // We only need the args for the offline commands => move them into another class. We need the number of args though for pub sub
|
||||||
this.buffer_args = false;
|
this.buffer_args = false;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
this.sub_commands_left = args.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function OfflineCommand(command, args, callback) {
|
||||||
|
this.command = command;
|
||||||
|
this.args = args;
|
||||||
|
this.callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Command;
|
module.exports = {
|
||||||
|
Command: Command,
|
||||||
|
OfflineCommand: OfflineCommand
|
||||||
|
};
|
||||||
|
@@ -255,6 +255,32 @@ describe("client authentication", function () {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('pubsub working with auth', function (done) {
|
||||||
|
if (helper.redisProcess().spawnFailed()) this.skip();
|
||||||
|
|
||||||
|
var args = config.configureClient(parser, ip, {
|
||||||
|
password: auth
|
||||||
|
});
|
||||||
|
client = redis.createClient.apply(redis.createClient, args);
|
||||||
|
client.set('foo', 'bar');
|
||||||
|
client.subscribe('somechannel', 'another channel', function (err, res) {
|
||||||
|
client.once('ready', function () {
|
||||||
|
assert.strictEqual(client.pub_sub_mode, 1);
|
||||||
|
client.get('foo', function (err, res) {
|
||||||
|
assert.strictEqual(err.message, 'ERR only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
client.once('ready', function () {
|
||||||
|
// Coherent behavior with all other offline commands fires commands before emitting but does not wait till they return
|
||||||
|
assert.strictEqual(client.pub_sub_mode, 2);
|
||||||
|
client.ping(function () { // Make sure all commands were properly processed already
|
||||||
|
client.stream.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -33,7 +33,7 @@ describe("The 'hgetall' method", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('handles fetching keys set using an object', function (done) {
|
it('handles fetching keys set using an object', function (done) {
|
||||||
client.HMSET("msg_test", {message: "hello"}, helper.isString("OK"));
|
client.HMSET("msg_test", { message: "hello" }, helper.isString("OK"));
|
||||||
client.hgetall("msg_test", function (err, obj) {
|
client.hgetall("msg_test", function (err, obj) {
|
||||||
assert.strictEqual(1, Object.keys(obj).length);
|
assert.strictEqual(1, Object.keys(obj).length);
|
||||||
assert.strictEqual(obj.message, "hello");
|
assert.strictEqual(obj.message, "hello");
|
||||||
|
@@ -230,6 +230,9 @@ describe("connection tests", function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
client.on('error', function(err) {
|
client.on('error', function(err) {
|
||||||
|
if (err.code === 'ENETUNREACH') { // The test is run without a internet connection. Pretent it works
|
||||||
|
return done();
|
||||||
|
}
|
||||||
assert(/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message));
|
assert(/Redis connection in broken state: connection timeout.*?exceeded./.test(err.message));
|
||||||
// The code execution on windows is very slow at times
|
// The code execution on windows is very slow at times
|
||||||
var add = process.platform !== 'win32' ? 25 : 125;
|
var add = process.platform !== 'win32' ? 25 : 125;
|
||||||
|
@@ -303,12 +303,7 @@ describe("The node_redis client", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: we should only have a single subscription in this this
|
|
||||||
// test but unsubscribing from the single channel indicates
|
|
||||||
// that one subscriber still exists, let's dig into this.
|
|
||||||
describe("and it's subscribed to a channel", function () {
|
describe("and it's subscribed to a channel", function () {
|
||||||
// reconnect_select_db_after_pubsub
|
|
||||||
// Does not pass.
|
|
||||||
// "Connection in subscriber mode, only subscriber commands may be used"
|
// "Connection in subscriber mode, only subscriber commands may be used"
|
||||||
it("reconnects, unsubscribes, and can retrieve the pre-existing data", function (done) {
|
it("reconnects, unsubscribes, and can retrieve the pre-existing data", function (done) {
|
||||||
client.on("ready", function on_connect() {
|
client.on("ready", function on_connect() {
|
||||||
@@ -316,6 +311,28 @@ describe("The node_redis client", function () {
|
|||||||
|
|
||||||
client.on('unsubscribe', function (channel, count) {
|
client.on('unsubscribe', function (channel, count) {
|
||||||
// we should now be out of subscriber mode.
|
// we should now be out of subscriber mode.
|
||||||
|
assert.strictEqual(channel, "recon channel");
|
||||||
|
assert.strictEqual(count, 0);
|
||||||
|
client.set('foo', 'bar', helper.isString('OK', done));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
client.set("recon 1", "one");
|
||||||
|
client.subscribe("recon channel", function (err, res) {
|
||||||
|
// Do not do this in normal programs. This is to simulate the server closing on us.
|
||||||
|
// For orderly shutdown in normal programs, do client.quit()
|
||||||
|
client.stream.destroy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("reconnects, unsubscribes, and can retrieve the pre-existing data of a explicit channel", function (done) {
|
||||||
|
client.on("ready", function on_connect() {
|
||||||
|
client.unsubscribe('recon channel', helper.isNotError());
|
||||||
|
|
||||||
|
client.on('unsubscribe', function (channel, count) {
|
||||||
|
// we should now be out of subscriber mode.
|
||||||
|
assert.strictEqual(channel, "recon channel");
|
||||||
|
assert.strictEqual(count, 0);
|
||||||
client.set('foo', 'bar', helper.isString('OK', done));
|
client.set('foo', 'bar', helper.isString('OK', done));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -452,6 +469,54 @@ describe("The node_redis client", function () {
|
|||||||
client.mget("hello", 'world');
|
client.mget("hello", 'world');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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.
|
||||||
|
// Therefor 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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('idle', function () {
|
describe('idle', function () {
|
||||||
|
@@ -85,6 +85,29 @@ describe("publish/subscribe", function () {
|
|||||||
sub.subscribe(channel, channel2);
|
sub.subscribe(channel, channel2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('fires a subscribe event for each channel as buffer subscribed to even after reconnecting', function (done) {
|
||||||
|
var a = false;
|
||||||
|
sub.end(true);
|
||||||
|
sub = redis.createClient({
|
||||||
|
detect_buffers: true
|
||||||
|
});
|
||||||
|
sub.on("subscribe", function (chnl, count) {
|
||||||
|
if (chnl.inspect() === new Buffer([0xAA, 0xBB, 0x00, 0xF0]).inspect()) {
|
||||||
|
assert.equal(1, count);
|
||||||
|
if (a) {
|
||||||
|
return done();
|
||||||
|
}
|
||||||
|
sub.stream.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sub.on('reconnecting', function() {
|
||||||
|
a = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
sub.subscribe(new Buffer([0xAA, 0xBB, 0x00, 0xF0]), channel2);
|
||||||
|
});
|
||||||
|
|
||||||
it('receives messages on subscribed channel', function (done) {
|
it('receives messages on subscribed channel', function (done) {
|
||||||
var end = helper.callFuncAfter(done, 2);
|
var end = helper.callFuncAfter(done, 2);
|
||||||
sub.on("subscribe", function (chnl, count) {
|
sub.on("subscribe", function (chnl, count) {
|
||||||
@@ -199,6 +222,216 @@ describe("publish/subscribe", function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("multiple subscribe / unsubscribe commands", function () {
|
||||||
|
|
||||||
|
it("reconnects properly with pub sub and select command", function (done) {
|
||||||
|
var end = helper.callFuncAfter(done, 2);
|
||||||
|
sub.select(3);
|
||||||
|
sub.set('foo', 'bar');
|
||||||
|
sub.subscribe('somechannel', 'another channel', function (err, res) {
|
||||||
|
end();
|
||||||
|
sub.stream.destroy();
|
||||||
|
});
|
||||||
|
assert(sub.ready);
|
||||||
|
sub.on('ready', function () {
|
||||||
|
sub.unsubscribe();
|
||||||
|
sub.del('foo');
|
||||||
|
sub.info(end);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not go into pubsub mode with unsubscribe commands", function (done) {
|
||||||
|
sub.on('unsubscribe', function (msg) {
|
||||||
|
// The unsubscribe should not be triggered, as there was no corresponding channel
|
||||||
|
throw new Error('Test failed');
|
||||||
|
});
|
||||||
|
sub.set('foo', 'bar');
|
||||||
|
sub.unsubscribe(function (err, res) {
|
||||||
|
assert.strictEqual(res, null);
|
||||||
|
});
|
||||||
|
sub.del('foo', done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("handles multiple channels with the same channel name properly, even with buffers", function (done) {
|
||||||
|
var channels = ['a', 'b', 'a', new Buffer('a'), 'c', 'b'];
|
||||||
|
var subscribed_channels = [1, 2, 2, 2, 3, 3];
|
||||||
|
var i = 0;
|
||||||
|
sub.subscribe(channels);
|
||||||
|
sub.on('subscribe', function (channel, count) {
|
||||||
|
if (Buffer.isBuffer(channel)) {
|
||||||
|
assert.strictEqual(channel.inspect(), new Buffer(channels[i]).inspect());
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(channel, channels[i].toString());
|
||||||
|
}
|
||||||
|
assert.strictEqual(count, subscribed_channels[i]);
|
||||||
|
i++;
|
||||||
|
});
|
||||||
|
sub.unsubscribe('a', 'c', 'b');
|
||||||
|
sub.get('foo', done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should only resubscribe to channels not unsubscribed earlier on a reconnect', function (done) {
|
||||||
|
sub.subscribe('/foo', '/bar');
|
||||||
|
sub.unsubscribe('/bar', function () {
|
||||||
|
pub.pubsub('channels', function (err, res) {
|
||||||
|
assert.deepEqual(res, ['/foo']);
|
||||||
|
sub.stream.destroy();
|
||||||
|
sub.once('ready', function () {
|
||||||
|
pub.pubsub('channels', function (err, res) {
|
||||||
|
assert.deepEqual(res, ['/foo']);
|
||||||
|
sub.unsubscribe('/foo', done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Withouth callbacks", function (done) {
|
||||||
|
function subscribe(channels) {
|
||||||
|
sub.unsubscribe(helper.isNull);
|
||||||
|
sub.subscribe(channels, helper.isNull);
|
||||||
|
}
|
||||||
|
var all = false;
|
||||||
|
var subscribeMsg = ['1', '3', '2', '5', 'test', 'bla'];
|
||||||
|
sub.on('subscribe', function(msg, count) {
|
||||||
|
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1);
|
||||||
|
if (subscribeMsg.length === 0 && all) {
|
||||||
|
assert.strictEqual(count, 3);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var unsubscribeMsg = ['1', '3', '2'];
|
||||||
|
sub.on('unsubscribe', function(msg, count) {
|
||||||
|
unsubscribeMsg.splice(unsubscribeMsg.indexOf(msg), 1);
|
||||||
|
if (unsubscribeMsg.length === 0) {
|
||||||
|
assert.strictEqual(count, 0);
|
||||||
|
all = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subscribe(['1', '3']);
|
||||||
|
subscribe(['2']);
|
||||||
|
subscribe(['5', 'test', 'bla']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without callbacks", function (done) {
|
||||||
|
function subscribe(channels) {
|
||||||
|
sub.unsubscribe();
|
||||||
|
sub.subscribe(channels);
|
||||||
|
}
|
||||||
|
var all = false;
|
||||||
|
var subscribeMsg = ['1', '3', '2', '5', 'test', 'bla'];
|
||||||
|
sub.on('subscribe', function(msg, count) {
|
||||||
|
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1);
|
||||||
|
if (subscribeMsg.length === 0 && all) {
|
||||||
|
assert.strictEqual(count, 3);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var unsubscribeMsg = ['1', '3', '2'];
|
||||||
|
sub.on('unsubscribe', function(msg, count) {
|
||||||
|
unsubscribeMsg.splice(unsubscribeMsg.indexOf(msg), 1);
|
||||||
|
if (unsubscribeMsg.length === 0) {
|
||||||
|
assert.strictEqual(count, 0);
|
||||||
|
all = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subscribe(['1', '3']);
|
||||||
|
subscribe(['2']);
|
||||||
|
subscribe(['5', 'test', 'bla']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unsubscribes, subscribes, unsubscribes... single and multiple entries mixed. Without callback and concret channels", function (done) {
|
||||||
|
function subscribe(channels) {
|
||||||
|
sub.unsubscribe(channels);
|
||||||
|
sub.unsubscribe(channels);
|
||||||
|
sub.subscribe(channels);
|
||||||
|
}
|
||||||
|
var all = false;
|
||||||
|
var subscribeMsg = ['1', '3', '2', '5', 'test', 'bla'];
|
||||||
|
sub.on('subscribe', function(msg, count) {
|
||||||
|
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1);
|
||||||
|
if (subscribeMsg.length === 0 && all) {
|
||||||
|
assert.strictEqual(count, 6);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var unsubscribeMsg = ['1', '3', '2', '5', 'test', 'bla'];
|
||||||
|
sub.on('unsubscribe', function(msg, count) {
|
||||||
|
var pos = unsubscribeMsg.indexOf(msg);
|
||||||
|
if (pos !== -1)
|
||||||
|
unsubscribeMsg.splice(pos, 1);
|
||||||
|
if (unsubscribeMsg.length === 0) {
|
||||||
|
all = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
subscribe(['1', '3']);
|
||||||
|
subscribe(['2']);
|
||||||
|
subscribe(['5', 'test', 'bla']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("unsubscribes, subscribes, unsubscribes... with pattern matching", function (done) {
|
||||||
|
function subscribe(channels, callback) {
|
||||||
|
sub.punsubscribe('prefix:*', helper.isNull);
|
||||||
|
sub.psubscribe(channels, function (err, res) {
|
||||||
|
helper.isNull(err);
|
||||||
|
if (callback) callback(err, res);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
var all = false;
|
||||||
|
var end = helper.callFuncAfter(done, 8);
|
||||||
|
var subscribeMsg = ['prefix:*', 'prefix:3', 'prefix:2', '5', 'test:a', 'bla'];
|
||||||
|
sub.on('psubscribe', function(msg, count) {
|
||||||
|
subscribeMsg.splice(subscribeMsg.indexOf(msg), 1);
|
||||||
|
if (subscribeMsg.length === 0) {
|
||||||
|
assert.strictEqual(count, 5);
|
||||||
|
all = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var rest = 1;
|
||||||
|
var unsubscribeMsg = ['prefix:*', 'prefix:*', 'prefix:*', '*'];
|
||||||
|
sub.on('punsubscribe', function(msg, count) {
|
||||||
|
unsubscribeMsg.splice(unsubscribeMsg.indexOf(msg), 1);
|
||||||
|
if (all) {
|
||||||
|
assert.strictEqual(unsubscribeMsg.length, 0);
|
||||||
|
assert.strictEqual(count, rest--); // Print the remaining channels
|
||||||
|
end();
|
||||||
|
} else {
|
||||||
|
assert.strictEqual(msg, 'prefix:*');
|
||||||
|
assert.strictEqual(count, rest++ - 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sub.on('pmessage', function (pattern, channel, msg) {
|
||||||
|
assert.strictEqual(msg, 'test');
|
||||||
|
assert.strictEqual(pattern, 'prefix:*');
|
||||||
|
assert.strictEqual(channel, 'prefix:1');
|
||||||
|
end();
|
||||||
|
});
|
||||||
|
|
||||||
|
subscribe(['prefix:*', 'prefix:3'], function () {
|
||||||
|
pub.publish('prefix:1', new Buffer('test'), function () {
|
||||||
|
subscribe(['prefix:2']);
|
||||||
|
subscribe(['5', 'test:a', 'bla'], function () {
|
||||||
|
assert(all);
|
||||||
|
});
|
||||||
|
sub.punsubscribe(function (err, res) {
|
||||||
|
assert(!err);
|
||||||
|
assert.strictEqual(res, 'bla');
|
||||||
|
assert(all);
|
||||||
|
all = false; // Make sure the callback is actually after the emit
|
||||||
|
end();
|
||||||
|
});
|
||||||
|
sub.pubsub('channels', function (err, res) {
|
||||||
|
assert.strictEqual(res.length, 0);
|
||||||
|
end();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('unsubscribe', function () {
|
describe('unsubscribe', function () {
|
||||||
it('fires an unsubscribe event', function (done) {
|
it('fires an unsubscribe event', function (done) {
|
||||||
sub.on("subscribe", function (chnl, count) {
|
sub.on("subscribe", function (chnl, count) {
|
||||||
@@ -237,22 +470,27 @@ describe("publish/subscribe", function () {
|
|||||||
it('executes callback when unsubscribe is called and there are no subscriptions', function (done) {
|
it('executes callback when unsubscribe is called and there are no subscriptions', function (done) {
|
||||||
pub.unsubscribe(function (err, results) {
|
pub.unsubscribe(function (err, results) {
|
||||||
assert.strictEqual(null, results);
|
assert.strictEqual(null, results);
|
||||||
return done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('psubscribe', function () {
|
describe('psubscribe', function () {
|
||||||
// test motivated by issue #753
|
|
||||||
it('allows all channels to be subscribed to using a * pattern', function (done) {
|
it('allows all channels to be subscribed to using a * pattern', function (done) {
|
||||||
sub.psubscribe('*');
|
sub.end(false);
|
||||||
sub.on("pmessage", function(pattern, channel, message) {
|
sub = redis.createClient({
|
||||||
assert.strictEqual(pattern, '*');
|
return_buffers: true
|
||||||
assert.strictEqual(channel, '/foo');
|
});
|
||||||
assert.strictEqual(message, 'hello world');
|
sub.on('ready', function () {
|
||||||
return done();
|
sub.psubscribe('*');
|
||||||
|
sub.on("pmessage", function(pattern, channel, message) {
|
||||||
|
assert.strictEqual(pattern.inspect(), new Buffer('*').inspect());
|
||||||
|
assert.strictEqual(channel.inspect(), new Buffer('/foo').inspect());
|
||||||
|
assert.strictEqual(message.inspect(), new Buffer('hello world').inspect());
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
pub.publish('/foo', 'hello world');
|
||||||
});
|
});
|
||||||
pub.publish('/foo', 'hello world');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -264,7 +502,7 @@ describe("publish/subscribe", function () {
|
|||||||
it('executes callback when punsubscribe is called and there are no subscriptions', function (done) {
|
it('executes callback when punsubscribe is called and there are no subscriptions', function (done) {
|
||||||
pub.punsubscribe(function (err, results) {
|
pub.punsubscribe(function (err, results) {
|
||||||
assert.strictEqual(null, results);
|
assert.strictEqual(null, results);
|
||||||
return done(err);
|
done(err);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -330,19 +568,15 @@ describe("publish/subscribe", function () {
|
|||||||
}, 40);
|
}, 40);
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Fix pub sub
|
it("should not publish a message without any publish command", function (done) {
|
||||||
// And there's more than just those two issues
|
pub.set('foo', 'message');
|
||||||
describe.skip('FIXME: broken pub sub', function () {
|
pub.set('bar', 'hello');
|
||||||
|
pub.mget('foo', 'bar');
|
||||||
it("should not publish a message without any publish command", function (done) {
|
pub.subscribe('channel', function () {
|
||||||
pub.set('foo', 'message');
|
setTimeout(done, 50);
|
||||||
pub.set('bar', 'hello');
|
});
|
||||||
pub.mget('foo', 'bar');
|
pub.on('message', function (msg) {
|
||||||
pub.subscribe('channel');
|
done(new Error('This message should not have been published: ' + msg));
|
||||||
pub.on('message', function (msg) {
|
|
||||||
done(new Error('This message should not have been published: ' + msg));
|
|
||||||
});
|
|
||||||
setTimeout(done, 200);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user