Commit 29ea901b authored by Pieter Noordhuis's avatar Pieter Noordhuis
Browse files

Fix the async free() and disconnect() functions

To make sure that these functions can also be called from functions
other than command callbacks, the flag IN_CALLBACK is introduced that
holds whether the context is currently executing a callback. If so,
redisAsyncFree() and redisAsyncDisconnect() should delegate their task
to the reply processor to avoid segfaults.
parent c882a362
...@@ -169,33 +169,19 @@ static void __redisAsyncFree(redisAsyncContext *ac) { ...@@ -169,33 +169,19 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
redisFree(c); redisFree(c);
} }
/* Free's the async context. When REDIS_CONNECTED is set, it could only have /* Free the async context. When this function is called from a callback,
* been called from a command callback so we need to let control return to * control needs to be returned to redisProcessCallbacks() before actual
* redisProcessCallbacks() before free'ing can continue. * free'ing. To do so, a flag is set on the context which is picked up by
* * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
* When REDIS_CONNECTED is not set, the first write event has not yet fired and
* we can free immediately. */
void redisAsyncFree(redisAsyncContext *ac) { void redisAsyncFree(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
if (c->flags & REDIS_CONNECTED) { if (c->flags & REDIS_IN_CALLBACK) {
c->flags |= REDIS_FREEING; c->flags |= REDIS_FREEING;
} else { } else {
__redisAsyncFree(ac); __redisAsyncFree(ac);
} }
} }
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
* from being issued, but tries to flush the output buffer and execute
* callbacks for all remaining replies.
*
* This functions is generally called from within a callback, so the
* processCallbacks function will pick up the flag when there are no
* more replies. */
void redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
c->flags |= REDIS_DISCONNECTING;
}
/* Helper function to make the disconnect happen and clean up. */ /* Helper function to make the disconnect happen and clean up. */
static void __redisAsyncDisconnect(redisAsyncContext *ac) { static void __redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
...@@ -214,14 +200,32 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) { ...@@ -214,14 +200,32 @@ static void __redisAsyncDisconnect(redisAsyncContext *ac) {
/* Execute pending callbacks with NULL reply. */ /* Execute pending callbacks with NULL reply. */
while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) { while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK) {
if (cb.fn != NULL) if (cb.fn != NULL) {
c->flags |= REDIS_IN_CALLBACK;
cb.fn(ac,NULL,cb.privdata); cb.fn(ac,NULL,cb.privdata);
c->flags &= ~REDIS_IN_CALLBACK;
}
} }
} }
__redisAsyncFree(ac); __redisAsyncFree(ac);
} }
/* Tries to do a clean disconnect from Redis, meaning it stops new commands
* from being issued, but tries to flush the output buffer and execute
* callbacks for all remaining replies. When this function is called from a
* callback, there might be more replies and we can safely defer disconnecting
* to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
* when there are no pending callbacks. */
void redisAsyncDisconnect(redisAsyncContext *ac) {
redisContext *c = &(ac->c);
if (c->flags & REDIS_IN_CALLBACK || ac->replies.head != NULL) {
c->flags |= REDIS_DISCONNECTING;
} else {
__redisAsyncDisconnect(ac);
}
}
void redisProcessCallbacks(redisAsyncContext *ac) { void redisProcessCallbacks(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb;
...@@ -245,7 +249,9 @@ void redisProcessCallbacks(redisAsyncContext *ac) { ...@@ -245,7 +249,9 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
/* Shift callback and execute it */ /* Shift callback and execute it */
assert(__redisShiftCallback(&ac->replies,&cb) == REDIS_OK); assert(__redisShiftCallback(&ac->replies,&cb) == REDIS_OK);
if (cb.fn != NULL) { if (cb.fn != NULL) {
c->flags |= REDIS_IN_CALLBACK;
cb.fn(ac,reply,cb.privdata); cb.fn(ac,reply,cb.privdata);
c->flags &= ~REDIS_IN_CALLBACK;
/* Proceed with free'ing when redisAsyncFree() was called. */ /* Proceed with free'ing when redisAsyncFree() was called. */
if (c->flags & REDIS_FREEING) { if (c->flags & REDIS_FREEING) {
......
...@@ -68,6 +68,9 @@ ...@@ -68,6 +68,9 @@
* up as soon as possible. */ * up as soon as possible. */
#define REDIS_FREEING 0x8 #define REDIS_FREEING 0x8
/* Flag that is set when an async callback is executed. */
#define REDIS_IN_CALLBACK 0x10
#define REDIS_REPLY_STRING 1 #define REDIS_REPLY_STRING 1
#define REDIS_REPLY_ARRAY 2 #define REDIS_REPLY_ARRAY 2
#define REDIS_REPLY_INTEGER 3 #define REDIS_REPLY_INTEGER 3
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment