-
Notifications
You must be signed in to change notification settings - Fork 128
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PubSub: do not depend on messages arriving in order to get control #49
Comments
I think here were have 2 (almost opposite) situations mixed together:
I think the 2nd one (and not missign incoming messages) could be resolved by just using |
@Nicowcow after thinking about it for a while I figure out that I can't understand your request. Current API seems to cover redis semantics in full. I'm not sure I understood your monad proposal. What would it do that can't be done with current interface? Also, would it just hang at the end if not all subscriptions were unsubscribed from? |
@k-bx I fear more and more that I have been missing something about the
|
@qrilka do you mean the |
@Nicowcow you are correct that Hedis does not allow you to re-take control from pubSub unless a message arrives. This is arguably a bug, and at least a weakness of the API. The question would be how do we get control? What does the API look like? A simple timeout that unsubscribes all channels should be relatively easy to implement. But I think it would get really messy if we had different timeouts for different channels/patterns. #7 is related to this. |
@Nicowcow from your scenario, it's still not clear, how do you determine when |
It could be some external event. If you want a particular example, imaging that we have a cluster managed by e.g. zookeeper. Each node publishes messages to a separate channel. Lets initially there is only one node, sending to "node1" channel, so we open a connection to redis and subscribe to the channel:
(Note that redis answer contains the channel I just subscribed. After receiving the answer I can know for sure that subscription was successful. It is not possible with hedis, there is no way to know that the answer arrived, see #28, which is not just a documentation bug in my opinion.) Now other node comes online (channel "node2") and announces itself via zookeeper. Lets subscribe to the channel:
Now the first node goes down (e.g. crashes, or is just removed from the cluster), so zookeeper notifies us about that, we want to unsubscribe from its channel:
(Again, when I got the answer, I can know that unsubscribing was successful, and no more messages from that channel will come. It is not possible with hedis, see #28 ) Unfortunately the same subscribe/unsubscribe sequence is impossible with hedis because the API is too restrictive. As mentioned in #60 redis pub/sub is asynchronous, but the issue is that hedis API is not! I can't subscribe at any point of time! Yes, there is a number of possible workarounds. E.g. I can use a separate connection per channel, or I can send a wake-up message to myself to get control. But they are ugly. Possible API change could be to expose an IO-based pub/sub handle for sending subscribe and unsubscribe commands:
|
First of all: Is there a Redis client in another language that has a really nice pubSub API that we might want to copy? @Yuras SUBSCRIBE is a "special" redis command: It changes the "mode" of the connection and subsequently only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT are allowed on the connection. After all channels are unsubscribed, the connection returns to "normal mode". So while your proposal would work in principle, it would now be possible for the user to put "forbidden" (error-producing) commands between SUBSCRIBE/UNSUBSCRIBE. Regarding the Nodes+Zookeeper scenario, wouldn't it be simpler to use PSUBSCRIBE "node*"? I think that changing subscriptions while "in pubSub mode" is somewhat of a Redis anti-pattern. There are so many pitfalls and intricacies that it might be better to disallow it completely. Again, I would be interested to see a shining example from another language that gets pubSub right. |
thanks @Yuras for the redis comparison. There are indeed workarounds, though nothing feels quite right. On top of that, if I'm correct, all those workarounds make you lose the safety provided by the |
@informatikr thanks for the input, and for taking the time to think about it. I had never considered changing subscriptions when in pubsub mode as an antipattern. Then, not allowing a change of subscription would be more of a feature than a bug, though we are still prevented from publishing until a new message arrives. |
@Nicowcow thanks for the problem explanation. I see now that indeed, we would need to make a two-way socket connection somehow in order to achieve "full power" which you'd like to use in this scenario. One thing to try out would be to add a new async exception, which would pause msg-awaiting and would send subscribe/unsubscribe messages. I'm not sure it would work correctly, but it's worth a try. |
@Nicowcow "Anti-pattern" might be a strong word and is just my impression that isn't even backed by using it in practice. After all, it is possible and allowed in Redis. But a combination of
also gets the job done and is, at least in my opinion, clearer. Of course, the issue remains that we can not gain control over subscriptions until a message arrives (without ugly hacks). |
Python? https://pypi.python.org/pypi/redis
I never used python, but from documentation I see that it supports all the necessary functionality.
In my example
It is just another workaround. There could not be any naming schema. Or we just don't want to subscribe to all nodes. Or whatever. |
Also, python redis library supports reading subscribe/unsubscribe confirmation:
|
@Yuras it's not clear to me how python code does what we want. Wouldn't |
You should probably read the docs I liked to:
In the example I provided I simply fork a worker thread to read messages:
Yes, I understand that. And that is exactly what I want too. |
@Yuras reading python client source code doesn't show that it could resolve #28 somehow, what it does is just sending commands (see https://github.com/andymccurdy/redis-py/blob/master/redis/connection.py#L537) without doing any response reads. |
@qrilka It is not necessary to copy python API exactly. As I mentioned a number of times, |
Indeed what I want is to be able to unsubscribe based on an external event, but I realized above that publishing based on an external event is probably what even more people would like. Hedis is pretty conservative in what modules it exposes, but maybe creating a simple API that wraps the raw pub sub redis calls would be enough to start with, and give people more freedom (it'd still be in the Redis monad, so the users would have to make sure they don't break anything). From there you'd just have a few internal functions like |
A while ago I made some changes to it to address this concern. I haven't tested it extensively though: k0001@4b64450?w=1 |
To implement issue informatikr#49, add a new multithreaded Pub/Sub message processing feature. The main benefit over the existing Pub/Sub code (which is left unchanged) is: - you can make subscription changes at any time - you can safely recover from networking errors such as the redis servier dying - you can detect when Redis has actually processed a subscription request and handlers will now start receiving messages.
I implemented this feature in commit #77. I went through several designs. I initially tried an approach like @nmattia suggested with just a few internal functions, but in order to work properly with multiple threads you essentially need raw access to the underlying socket, which should not be exposed as part of the public API. So the design I settled on is one thread for reading, one thread for writing, and a controller that allows you to interact with the reading/writing threads. I also wanted to properly handle network disconnects and resubscribing to channels after a disconnect. It made sense to make that directly part of the threads since the underlying implementation is tracking channels anyway. |
Hedis' PubSub interface is really neat, making sure you can never exit without having unsubscribed to all the channels. Unfortunately (unless I missed something) you can never gain control unless a message arrives. A situation is the following: you subscribe to a channel, exchange with redis, and at some point want to unsubscribe; unfortunately you have to wait until the next message arrives in order for your code to be executed. This is clear from the type signature:
You could prevent your callback from returning, but it's not particularly elegant, and you'd miss all the further incoming messages (really not an option).
The interface is actually really nice, but I think it could somehow be made more powerful. It's annoying to have to use "bare metal" commands from
Core
with in order to gain more flexibility. I don't have a solution for this, but it's probably worth thinking about.pubSub
could look more likerunRedis
, except that it will lock you in if you are still subscribed to any channel, and will prevent you from running any non-pubsub command.That is, there could be a
PubSub
monad (just a newtype wrapperRedis
just allowing subscription/unsubscription) andpubSub
would have typeso that in case you are not fully unsubscribed when returning (that is, if the result of the last command did not return a null number of subscriptions), it would return a
Left (PubSub a)
which you'll have to handle explicitly. This should also help with #28 since all requests can be handled explicitly beforepubSub
returns.This is not as nice as the monoid interface, and probably not the best alternative, but I think it would work. Please let me know if I missed something very simple, and please share your thoughts in general. :)
The text was updated successfully, but these errors were encountered: