-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Add finalizer support to async Redis client #926
base: master
Are you sure you want to change the base?
Conversation
@michael-grunder have you had a chance to take a look at this? Thank you! |
Hi @tudor, sorry for the delay. The logic looks good at first glance. We have to be pretty careful about backward compatibility now that we've released v1.0.0, but I will test the logic in detail over the next couple of days. |
@michael-grunder I don't think there are any backwards compatibility issues. The only thing that changes in a potentially backwards incompatible way is |
I guess code that explicitly accesses |
I was just about to open an issue asking for this yesterday. I presume this is to support knowing when hiredis forgets the callback.
|
|
async.c
Outdated
@@ -782,6 +809,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void | |||
if (de != NULL) { | |||
existcb = dictGetEntryVal(de); | |||
cb.pending_subs = existcb->pending_subs + 1; | |||
__redisRunFinalizer(ac,existcb); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be prudent to check here if the contents of extcb
is different from cb
? In particular the privdata
member, but probably also the callback and finalizer, in case the caller uses dynamically created stubs and NULL
for privdata
.
A caller might re-register to the same calback, particularly in light of the rather vague docuementation of hiredis
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dynamically creating functions is hard, so if you want dynamically-allocated state, you'll have trampoline functions and keep all the state in privdata
. So a check for privdata
would probably suffice.
But I stand by the belief that, as far as hiredis is concerned, we should keep the interface simple: if you replace a callback, the old one is gone (and finalized), and the new one takes its place.
(Ideally, hiredis wouldn't replace the callback if you use multiple callbacks to subscribe to the same channel-- it would just call all of them! but I understand why they made the decision that they did, and having multiple callbacks called with the same redisReply*
would mean that you can't mutate the redisReply
any more and you have to treat it as const.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree in principle, but async programming is hard, and as a precautionary principle, I would make sure that I didn't finalize the callback that I am installing. Checking that the finalize calllback and the privdata are different would make sense.
Alternatively, we could improve upon the barely existing documentation by stating that calling redisAsyncCall*()
to subscribe, more than once, to the same pattern/channel was undefined.
Ideally, there should be separate api functions to subscribe/unsubscribe from channels/patterns because the calling semantics are completely different than for regular calls.
async.c
Outdated
@@ -782,6 +809,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void | |||
if (de != NULL) { | |||
existcb = dictGetEntryVal(de); | |||
cb.pending_subs = existcb->pending_subs + 1; | |||
__redisRunFinalizer(ac,existcb); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree in principle, but async programming is hard, and as a precautionary principle, I would make sure that I didn't finalize the callback that I am installing. Checking that the finalize calllback and the privdata are different would make sense.
Alternatively, we could improve upon the barely existing documentation by stating that calling redisAsyncCall*()
to subscribe, more than once, to the same pattern/channel was undefined.
Ideally, there should be separate api functions to subscribe/unsubscribe from channels/patterns because the calling semantics are completely different than for regular calls.
async.c
Outdated
@@ -782,6 +809,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void | |||
if (de != NULL) { | |||
existcb = dictGetEntryVal(de); | |||
cb.pending_subs = existcb->pending_subs + 1; | |||
__redisRunFinalizer(ac,existcb); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (existcb->privdata != cb.privdata)
__redisRunFinalizer(ac, existcb);
…t tests it and also demonstrates pubsub
@kristjanvalur sorry for the long delay, I made the change you requested. PTAL? |
LGTM, although honestly, not much is happening in this repo. I've got a bunch of PRs that have been stuck for months, including a bunch of defects/fixes. |
@michael-grunder PTAL? C++ programmers worldwide would thank you :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't mind making those small changes I will get this merged tomorrow morning (West coast, PDT).
redisFinalizerCallback *finalizer; | ||
int pending_subs; | ||
void *privdata; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we have to put the new member at the end of the structure, as the current changes breaks ABI compatibility by inserting a member in the middle.
redisFinalizerCallback *finalizer; | |
int pending_subs; | |
void *privdata; | |
int pending_subs; | |
void *privdata; | |
redisFinalizerCallback *finalizer; |
@@ -502,11 +521,12 @@ static int redisIsSubscribeReply(redisReply *reply) { | |||
|
|||
void redisProcessCallbacks(redisAsyncContext *ac) { | |||
redisContext *c = &(ac->c); | |||
redisCallback cb = {NULL, NULL, 0, NULL}; | |||
redisCallback cb = {NULL, NULL, NULL, 0, NULL}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're placing the new callback at the end.
redisCallback cb = {NULL, NULL, NULL, 0, NULL}; | |
redisCallback cb = {NULL, NULL, 0, NULL, NULL}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
redisCallback cb = {0};
would be fine too.
TARGET_LINK_LIBRARIES(example-libevent hiredis event) | ||
ADD_EXECUTABLE(example-libevent-pubsub example-libevent-pubsub.c) | ||
TARGET_LINK_LIBRARIES(example-libevent-pubsub hiredis event) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also update the Makefile
with this new example, although I'm happy to do that after we merge the PR.
} | ||
|
||
void getFinalizer(redisAsyncContext *c, void *privdata) { | ||
printf("Get finalizer called\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to avoid the warning
printf("Get finalizer called\n"); | |
(void)privdata; | |
printf("Get finalizer called\n"); |
} | ||
|
||
void subscribeFinalizer(redisAsyncContext *c, void *privdata) { | ||
printf("Subscribe finalizer called\n"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
printf("Subscribe finalizer called\n"); | |
(void)privdata; | |
printf("Subscribe finalizer called\n"); |
@tudor are you still interested in finishing this? |
@zuiderkwast No, I'm not planning to work on this any more. I ended up having to parse the request and response (to know whether it's pub/sub or regular command) anyway for a different reason, in which case I can tell when the callback may be called multiple times. |
That's a shame, it looked like a worthwhile addition. If there is interest, I can pick this up. |
@kristjanvalur I was going to pick it up myself, but you can do it if you want. We want it for hiredis-cluster, so we can implement pubsub without actually looking at the commands sent by the user. We haven't fully decided if this is enough though. |
No, in that case it is best you do it, I haven't touched hiredis for more than two years and am quite rusty :) |
Hehe, I'm not sure I'm less rusty with hiredis internals. :-) I was just going to rebase and apply Michael's comments. I'll invite you for review. |
This PR doesn't correctly handle SUBSCRIBE with multiple channels with finalizer. I've implemented this now in #1173. It's ready for review. Please have a look. (I'll try to get time to implement the RESET command too, and to allow calling commands while in monitor mode.) I had to rewrite a lot, because a callback can be used for multiple channels, which can be usubscribed independenly. To keep track of the last reference to a callback (for calling the finalizer at the right time) I've added a reference counter in the callback struct. |
Also add libevent example that tests it and also demonstrates pubsub.
Fixes #925