You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-07 13:22:56 +03:00
Fix pub sub mode
There is likely a better and more performant way to fix this but this works so far and should be good enough to release and improve later. Make test more robust Add another test
This commit is contained in:
@@ -255,6 +255,32 @@ describe("client authentication", function () {
|
||||
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) {
|
||||
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) {
|
||||
assert.strictEqual(1, Object.keys(obj).length);
|
||||
assert.strictEqual(obj.message, "hello");
|
||||
|
@@ -230,6 +230,9 @@ describe("connection tests", function () {
|
||||
});
|
||||
|
||||
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));
|
||||
// The code execution on windows is very slow at times
|
||||
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 () {
|
||||
// reconnect_select_db_after_pubsub
|
||||
// Does not pass.
|
||||
// "Connection in subscriber mode, only subscriber commands may be used"
|
||||
it("reconnects, unsubscribes, and can retrieve the pre-existing data", function (done) {
|
||||
client.on("ready", function on_connect() {
|
||||
@@ -316,6 +311,28 @@ describe("The node_redis client", function () {
|
||||
|
||||
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("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));
|
||||
});
|
||||
});
|
||||
@@ -452,6 +469,54 @@ describe("The node_redis client", function () {
|
||||
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 () {
|
||||
|
@@ -85,6 +85,29 @@ describe("publish/subscribe", function () {
|
||||
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) {
|
||||
var end = helper.callFuncAfter(done, 2);
|
||||
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 () {
|
||||
it('fires an unsubscribe event', function (done) {
|
||||
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) {
|
||||
pub.unsubscribe(function (err, results) {
|
||||
assert.strictEqual(null, results);
|
||||
return done(err);
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('psubscribe', function () {
|
||||
// test motivated by issue #753
|
||||
it('allows all channels to be subscribed to using a * pattern', function (done) {
|
||||
sub.psubscribe('*');
|
||||
sub.on("pmessage", function(pattern, channel, message) {
|
||||
assert.strictEqual(pattern, '*');
|
||||
assert.strictEqual(channel, '/foo');
|
||||
assert.strictEqual(message, 'hello world');
|
||||
return done();
|
||||
sub.end(false);
|
||||
sub = redis.createClient({
|
||||
return_buffers: true
|
||||
});
|
||||
sub.on('ready', function () {
|
||||
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) {
|
||||
pub.punsubscribe(function (err, results) {
|
||||
assert.strictEqual(null, results);
|
||||
return done(err);
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -330,19 +568,15 @@ describe("publish/subscribe", function () {
|
||||
}, 40);
|
||||
});
|
||||
|
||||
// TODO: Fix pub sub
|
||||
// And there's more than just those two issues
|
||||
describe.skip('FIXME: broken pub sub', function () {
|
||||
|
||||
it("should not publish a message without any publish command", function (done) {
|
||||
pub.set('foo', 'message');
|
||||
pub.set('bar', 'hello');
|
||||
pub.mget('foo', 'bar');
|
||||
pub.subscribe('channel');
|
||||
pub.on('message', function (msg) {
|
||||
done(new Error('This message should not have been published: ' + msg));
|
||||
});
|
||||
setTimeout(done, 200);
|
||||
it("should not publish a message without any publish command", function (done) {
|
||||
pub.set('foo', 'message');
|
||||
pub.set('bar', 'hello');
|
||||
pub.mget('foo', 'bar');
|
||||
pub.subscribe('channel', function () {
|
||||
setTimeout(done, 50);
|
||||
});
|
||||
pub.on('message', function (msg) {
|
||||
done(new Error('This message should not have been published: ' + msg));
|
||||
});
|
||||
});
|
||||
|
||||
|
Reference in New Issue
Block a user