Generated with:

local r = require "lredis.cqueues".connect_tcp()
r:call("multi")
r:call("subscribe", "test")
r:call("publish", "test", "a message")
r:call("unsubscribe", "test")
r:call("exec")

What goes over the socket:

connect(127.0.0.1/[127.0.0.1]:6379): ready
write(127.0.0.1/[127.0.0.1]:6379): sent 15 bytes
  000000  2a 31 0d 0a 24 35 0d 0a  6d 75 6c 74 69 0d 0a     |*1..$5..multi.. |
read(127.0.0.1/[127.0.0.1]:6379): rcvd 5 bytes
  000000  2b 4f 4b 0d 0a                                    |+OK..           |
write(127.0.0.1/[127.0.0.1]:6379): sent 29 bytes
  000000  2a 32 0d 0a 24 39 0d 0a  73 75 62 73 63 72 69 62  |*2..$9..subscrib|
  000010  65 0d 0a 24 34 0d 0a 74  65 73 74 0d 0a           |e..$4..test..   |
read(127.0.0.1/[127.0.0.1]:6379): rcvd 9 bytes
  000000  2b 51 55 45 55 45 44 0d  0a                       |+QUEUED..       |
write(127.0.0.1/[127.0.0.1]:6379): sent 42 bytes
  000000  2a 33 0d 0a 24 37 0d 0a  70 75 62 6c 69 73 68 0d  |*3..$7..publish.|
  000010  0a 24 34 0d 0a 74 65 73  74 0d 0a 24 39 0d 0a 61  |.$4..test..$9..a|
  000020  20 6d 65 73 73 61 67 65  0d 0a                    |.message..      |
read(127.0.0.1/[127.0.0.1]:6379): rcvd 9 bytes
  000000  2b 51 55 45 55 45 44 0d  0a                       |+QUEUED..       |
write(127.0.0.1/[127.0.0.1]:6379): sent 32 bytes
  000000  2a 32 0d 0a 24 31 31 0d  0a 75 6e 73 75 62 73 63  |*2..$11..unsubsc|
  000010  72 69 62 65 0d 0a 24 34  0d 0a 74 65 73 74 0d 0a  |ribe..$4..test..|
read(127.0.0.1/[127.0.0.1]:6379): rcvd 9 bytes
  000000  2b 51 55 45 55 45 44 0d  0a                       |+QUEUED..       |
write(127.0.0.1/[127.0.0.1]:6379): sent 14 bytes
  000000  2a 31 0d 0a 24 34 0d 0a  65 78 65 63 0d 0a        |*1..$4..exec..  |
read(127.0.0.1/[127.0.0.1]:6379): rcvd 119 bytes
  000000  2a 33 0d 0a 2a 33 0d 0a  24 39 0d 0a 73 75 62 73  |*3..*3..$9..subs|
  000010  63 72 69 62 65 0d 0a 24  34 0d 0a 74 65 73 74 0d  |cribe..$4..test.|
  000020  0a 3a 31 0d 0a 2a 33 0d  0a 24 37 0d 0a 6d 65 73  |.:1..*3..$7..mes|
  000030  73 61 67 65 0d 0a 24 34  0d 0a 74 65 73 74 0d 0a  |sage..$4..test..|
  000040  24 39 0d 0a 61 20 6d 65  73 73 61 67 65 0d 0a 3a  |$9..a.message..:|
  000050  31 0d 0a 2a 33 0d 0a 24  31 31 0d 0a 75 6e 73 75  |1..*3..$11..unsu|
  000060  62 73 63 72 69 62 65 0d  0a 24 34 0d 0a 74 65 73  |bscribe..$4..tes|
  000070  74 0d 0a 3a 30 0d 0a                              |t..:0..         |

You'll notice that the exec response returned is 3 elements long: - first element is {"subscribe", "test", 1} - second element is {"message", "test", "a message"} - third element is 1 (probably the result from the publish?)

After that the server sends a 3 element array with only one element: {"unsubscribe", "test", 0}

Comment From: badboy

Sounds good to me. You're just tripping over the unusual behaviour of pubsub inside a transaction.

What happens: 1. You get the response to your subscribe: the usual message with type, channel and number of subscriptions 2. You get the published message from your own publish call. Published messages are queued immediately on publish 3. You get the response of your publish call: the number of clients that received the message, which is only your own 4. You get the message as a result of your unsubscribe with type, the channel and the number of subscriptions left

Normally only a few selected commands are allowed in subscribed mode, but because this is only checked on receive of a command, which is inside a transaction for you and thus only queued, not executed, Redis will happily execute it on EXEC (at which point it does not recheck your client's status)

Comment From: daurnimator

It sounds like one of the following should happen: - when publish sends the message, the 'length' of the EXEC result needs to be incremented. - a publish to yourself while in a transaction should be an error - a subscribe while in a transaction should be an error

Comment From: badboy

Ah, indeed the initial multi-bulk response length is of course wrong. 1. You do not know whether or not a publish in the queue generates a message 2. That might be possible, but I didn't check the code there. 3. I bet some people are relying on it :D

All in all: we can't stop everyting bad from the user.

Comment From: antirez

Hello, it looks sensible to deny *SUBSCRIBE inside transactions starting from Redis 3.2 RC2. Do you agree?

Comment From: daurnimator

Hello, it looks sensible to deny *SUBSCRIBE inside transactions starting from Redis 3.2 RC2. Do you agree?

No objections here. It already is an error for e.g. lua scripts inside redis.

Comment From: daurnimator

Btw, what postgres does is that LISTEN only takes effect at time of commit: http://www.postgresql.org/docs/current/static/sql-listen.html

LISTEN takes effect at transaction commit. If LISTEN or UNLISTEN is executed within a transaction that later rolls back, the set of notification channels being listened to is unchanged

Comment From: itamarhaber

Erroring makes it easier to reason about compare to delaying the subscribe until exec (no offense to pgsql ;)).

On Mon, Apr 11, 2016 at 7:20 AM, daurnimator notifications@github.com wrote:

Btw, what postgres does is that LISTEN only takes effect at time of commit: http://www.postgresql.org/docs/current/static/sql-listen.html

LISTEN takes effect at transaction commit. If LISTEN or UNLISTEN is executed within a transaction that later rolls back, the set of notification channels being listened to is unchanged

— You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub https://github.com/antirez/redis/issues/2967#issuecomment-208158707