]>
git.saurik.com Git - redis.git/blob - deps/hiredis/async.c
2 * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
3 * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
10 * * Redistributions of source code must retain the above copyright notice,
11 * this list of conditions and the following disclaimer.
12 * * Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Redis nor the names of its contributors may be used
16 * to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
44 #define _EL_ADD_READ(ctx) do { \
45 if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
47 #define _EL_DEL_READ(ctx) do { \
48 if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
50 #define _EL_ADD_WRITE(ctx) do { \
51 if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
53 #define _EL_DEL_WRITE(ctx) do { \
54 if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
56 #define _EL_CLEANUP(ctx) do { \
57 if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
60 /* Forward declaration of function in hiredis.c */
61 void __redisAppendCommand(redisContext
*c
, char *cmd
, size_t len
);
63 /* Functions managing dictionary of callbacks for pub/sub. */
64 static unsigned int callbackHash(const void *key
) {
65 return dictGenHashFunction((unsigned char*)key
,sdslen((char*)key
));
68 static void *callbackValDup(void *privdata
, const void *src
) {
70 redisCallback
*dup
= malloc(sizeof(*dup
));
71 memcpy(dup
,src
,sizeof(*dup
));
75 static int callbackKeyCompare(void *privdata
, const void *key1
, const void *key2
) {
79 l1
= sdslen((sds
)key1
);
80 l2
= sdslen((sds
)key2
);
81 if (l1
!= l2
) return 0;
82 return memcmp(key1
,key2
,l1
) == 0;
85 static void callbackKeyDestructor(void *privdata
, void *key
) {
90 static void callbackValDestructor(void *privdata
, void *val
) {
95 static dictType callbackDict
= {
100 callbackKeyDestructor
,
101 callbackValDestructor
104 static redisAsyncContext
*redisAsyncInitialize(redisContext
*c
) {
105 redisAsyncContext
*ac
= realloc(c
,sizeof(redisAsyncContext
));
108 /* The regular connect functions will always set the flag REDIS_CONNECTED.
109 * For the async API, we want to wait until the first write event is
110 * received up before setting this flag, so reset it here. */
111 c
->flags
&= ~REDIS_CONNECTED
;
118 ac
->ev
.addRead
= NULL
;
119 ac
->ev
.delRead
= NULL
;
120 ac
->ev
.addWrite
= NULL
;
121 ac
->ev
.delWrite
= NULL
;
122 ac
->ev
.cleanup
= NULL
;
124 ac
->onConnect
= NULL
;
125 ac
->onDisconnect
= NULL
;
127 ac
->replies
.head
= NULL
;
128 ac
->replies
.tail
= NULL
;
129 ac
->sub
.invalid
.head
= NULL
;
130 ac
->sub
.invalid
.tail
= NULL
;
131 ac
->sub
.channels
= dictCreate(&callbackDict
,NULL
);
132 ac
->sub
.patterns
= dictCreate(&callbackDict
,NULL
);
136 /* We want the error field to be accessible directly instead of requiring
137 * an indirection to the redisContext struct. */
138 static void __redisAsyncCopyError(redisAsyncContext
*ac
) {
139 redisContext
*c
= &(ac
->c
);
141 ac
->errstr
= c
->errstr
;
144 redisAsyncContext
*redisAsyncConnect(const char *ip
, int port
) {
145 redisContext
*c
= redisConnectNonBlock(ip
,port
);
146 redisAsyncContext
*ac
= redisAsyncInitialize(c
);
147 __redisAsyncCopyError(ac
);
151 redisAsyncContext
*redisAsyncConnectUnix(const char *path
) {
152 redisContext
*c
= redisConnectUnixNonBlock(path
);
153 redisAsyncContext
*ac
= redisAsyncInitialize(c
);
154 __redisAsyncCopyError(ac
);
158 int redisAsyncSetConnectCallback(redisAsyncContext
*ac
, redisConnectCallback
*fn
) {
159 if (ac
->onConnect
== NULL
) {
162 /* The common way to detect an established connection is to wait for
163 * the first write event to be fired. This assumes the related event
164 * library functions are already set. */
171 int redisAsyncSetDisconnectCallback(redisAsyncContext
*ac
, redisDisconnectCallback
*fn
) {
172 if (ac
->onDisconnect
== NULL
) {
173 ac
->onDisconnect
= fn
;
179 /* Helper functions to push/shift callbacks */
180 static int __redisPushCallback(redisCallbackList
*list
, redisCallback
*source
) {
183 /* Copy callback from stack to heap */
184 cb
= malloc(sizeof(*cb
));
185 if (source
!= NULL
) {
186 memcpy(cb
,source
,sizeof(*cb
));
190 /* Store callback in list */
191 if (list
->head
== NULL
)
193 if (list
->tail
!= NULL
)
194 list
->tail
->next
= cb
;
199 static int __redisShiftCallback(redisCallbackList
*list
, redisCallback
*target
) {
200 redisCallback
*cb
= list
->head
;
202 list
->head
= cb
->next
;
203 if (cb
== list
->tail
)
206 /* Copy callback from heap to stack */
208 memcpy(target
,cb
,sizeof(*cb
));
215 static void __redisRunCallback(redisAsyncContext
*ac
, redisCallback
*cb
, redisReply
*reply
) {
216 redisContext
*c
= &(ac
->c
);
217 if (cb
->fn
!= NULL
) {
218 c
->flags
|= REDIS_IN_CALLBACK
;
219 cb
->fn(ac
,reply
,cb
->privdata
);
220 c
->flags
&= ~REDIS_IN_CALLBACK
;
224 /* Helper function to free the context. */
225 static void __redisAsyncFree(redisAsyncContext
*ac
) {
226 redisContext
*c
= &(ac
->c
);
231 /* Execute pending callbacks with NULL reply. */
232 while (__redisShiftCallback(&ac
->replies
,&cb
) == REDIS_OK
)
233 __redisRunCallback(ac
,&cb
,NULL
);
235 /* Execute callbacks for invalid commands */
236 while (__redisShiftCallback(&ac
->sub
.invalid
,&cb
) == REDIS_OK
)
237 __redisRunCallback(ac
,&cb
,NULL
);
239 /* Run subscription callbacks callbacks with NULL reply */
240 it
= dictGetIterator(ac
->sub
.channels
);
241 while ((de
= dictNext(it
)) != NULL
)
242 __redisRunCallback(ac
,dictGetEntryVal(de
),NULL
);
243 dictReleaseIterator(it
);
244 dictRelease(ac
->sub
.channels
);
246 it
= dictGetIterator(ac
->sub
.patterns
);
247 while ((de
= dictNext(it
)) != NULL
)
248 __redisRunCallback(ac
,dictGetEntryVal(de
),NULL
);
249 dictReleaseIterator(it
);
250 dictRelease(ac
->sub
.patterns
);
252 /* Signal event lib to clean up */
255 /* Execute disconnect callback. When redisAsyncFree() initiated destroying
256 * this context, the status will always be REDIS_OK. */
257 if (ac
->onDisconnect
&& (c
->flags
& REDIS_CONNECTED
)) {
258 if (c
->flags
& REDIS_FREEING
) {
259 ac
->onDisconnect(ac
,REDIS_OK
);
261 ac
->onDisconnect(ac
,(ac
->err
== 0) ? REDIS_OK
: REDIS_ERR
);
269 /* Free the async context. When this function is called from a callback,
270 * control needs to be returned to redisProcessCallbacks() before actual
271 * free'ing. To do so, a flag is set on the context which is picked up by
272 * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
273 void redisAsyncFree(redisAsyncContext
*ac
) {
274 redisContext
*c
= &(ac
->c
);
275 c
->flags
|= REDIS_FREEING
;
276 if (!(c
->flags
& REDIS_IN_CALLBACK
))
277 __redisAsyncFree(ac
);
280 /* Helper function to make the disconnect happen and clean up. */
281 static void __redisAsyncDisconnect(redisAsyncContext
*ac
) {
282 redisContext
*c
= &(ac
->c
);
284 /* Make sure error is accessible if there is any */
285 __redisAsyncCopyError(ac
);
288 /* For clean disconnects, there should be no pending callbacks. */
289 assert(__redisShiftCallback(&ac
->replies
,NULL
) == REDIS_ERR
);
291 /* Disconnection is caused by an error, make sure that pending
292 * callbacks cannot call new commands. */
293 c
->flags
|= REDIS_DISCONNECTING
;
296 /* For non-clean disconnects, __redisAsyncFree() will execute pending
297 * callbacks with a NULL-reply. */
298 __redisAsyncFree(ac
);
301 /* Tries to do a clean disconnect from Redis, meaning it stops new commands
302 * from being issued, but tries to flush the output buffer and execute
303 * callbacks for all remaining replies. When this function is called from a
304 * callback, there might be more replies and we can safely defer disconnecting
305 * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
306 * when there are no pending callbacks. */
307 void redisAsyncDisconnect(redisAsyncContext
*ac
) {
308 redisContext
*c
= &(ac
->c
);
309 c
->flags
|= REDIS_DISCONNECTING
;
310 if (!(c
->flags
& REDIS_IN_CALLBACK
) && ac
->replies
.head
== NULL
)
311 __redisAsyncDisconnect(ac
);
314 static int __redisGetSubscribeCallback(redisAsyncContext
*ac
, redisReply
*reply
, redisCallback
*dstcb
) {
315 redisContext
*c
= &(ac
->c
);
322 /* Custom reply functions are not supported for pub/sub. This will fail
323 * very hard when they are used... */
324 if (reply
->type
== REDIS_REPLY_ARRAY
) {
325 assert(reply
->elements
>= 2);
326 assert(reply
->element
[0]->type
== REDIS_REPLY_STRING
);
327 stype
= reply
->element
[0]->str
;
328 pvariant
= (tolower(stype
[0]) == 'p') ? 1 : 0;
331 callbacks
= ac
->sub
.patterns
;
333 callbacks
= ac
->sub
.channels
;
335 /* Locate the right callback */
336 assert(reply
->element
[1]->type
== REDIS_REPLY_STRING
);
337 sname
= sdsnewlen(reply
->element
[1]->str
,reply
->element
[1]->len
);
338 de
= dictFind(callbacks
,sname
);
340 memcpy(dstcb
,dictGetEntryVal(de
),sizeof(*dstcb
));
342 /* If this is an unsubscribe message, remove it. */
343 if (strcasecmp(stype
+pvariant
,"unsubscribe") == 0) {
344 dictDelete(callbacks
,sname
);
346 /* If this was the last unsubscribe message, revert to
347 * non-subscribe mode. */
348 assert(reply
->element
[2]->type
== REDIS_REPLY_INTEGER
);
349 if (reply
->element
[2]->integer
== 0)
350 c
->flags
&= ~REDIS_SUBSCRIBED
;
355 /* Shift callback for invalid commands. */
356 __redisShiftCallback(&ac
->sub
.invalid
,dstcb
);
361 void redisProcessCallbacks(redisAsyncContext
*ac
) {
362 redisContext
*c
= &(ac
->c
);
367 while((status
= redisGetReply(c
,&reply
)) == REDIS_OK
) {
369 /* When the connection is being disconnected and there are
370 * no more replies, this is the cue to really disconnect. */
371 if (c
->flags
& REDIS_DISCONNECTING
&& sdslen(c
->obuf
) == 0) {
372 __redisAsyncDisconnect(ac
);
376 /* When the connection is not being disconnected, simply stop
377 * trying to get replies and wait for the next loop tick. */
381 /* Even if the context is subscribed, pending regular callbacks will
382 * get a reply before pub/sub messages arrive. */
383 if (__redisShiftCallback(&ac
->replies
,&cb
) != REDIS_OK
) {
384 /* A spontaneous reply in a not-subscribed context can only be the
385 * error reply that is sent when a new connection exceeds the
386 * maximum number of allowed connections on the server side. This
387 * is seen as an error instead of a regular reply because the
388 * server closes the connection after sending it. To prevent the
389 * error from being overwritten by an EOF error the connection is
390 * closed here. See issue #43. */
391 if ( !(c
->flags
& REDIS_SUBSCRIBED
) && ((redisReply
*)reply
)->type
== REDIS_REPLY_ERROR
) {
392 c
->err
= REDIS_ERR_OTHER
;
393 snprintf(c
->errstr
,sizeof(c
->errstr
),"%s",((redisReply
*)reply
)->str
);
394 __redisAsyncDisconnect(ac
);
397 /* No more regular callbacks and no errors, the context *must* be subscribed. */
398 assert(c
->flags
& REDIS_SUBSCRIBED
);
399 __redisGetSubscribeCallback(ac
,reply
,&cb
);
403 __redisRunCallback(ac
,&cb
,reply
);
404 c
->reader
->fn
->freeObject(reply
);
406 /* Proceed with free'ing when redisAsyncFree() was called. */
407 if (c
->flags
& REDIS_FREEING
) {
408 __redisAsyncFree(ac
);
412 /* No callback for this reply. This can either be a NULL callback,
413 * or there were no callbacks to begin with. Either way, don't
414 * abort with an error, but simply ignore it because the client
415 * doesn't know what the server will spit out over the wire. */
416 c
->reader
->fn
->freeObject(reply
);
420 /* Disconnect when there was an error reading the reply */
421 if (status
!= REDIS_OK
)
422 __redisAsyncDisconnect(ac
);
425 /* Internal helper function to detect socket status the first time a read or
426 * write event fires. When connecting was not succesful, the connect callback
427 * is called with a REDIS_ERR status and the context is free'd. */
428 static int __redisAsyncHandleConnect(redisAsyncContext
*ac
) {
429 redisContext
*c
= &(ac
->c
);
431 if (redisCheckSocketError(c
,c
->fd
) == REDIS_ERR
) {
432 /* Try again later when connect(2) is still in progress. */
433 if (errno
== EINPROGRESS
)
436 if (ac
->onConnect
) ac
->onConnect(ac
,REDIS_ERR
);
437 __redisAsyncDisconnect(ac
);
441 /* Mark context as connected. */
442 c
->flags
|= REDIS_CONNECTED
;
443 if (ac
->onConnect
) ac
->onConnect(ac
,REDIS_OK
);
447 /* This function should be called when the socket is readable.
448 * It processes all replies that can be read and executes their callbacks.
450 void redisAsyncHandleRead(redisAsyncContext
*ac
) {
451 redisContext
*c
= &(ac
->c
);
453 if (!(c
->flags
& REDIS_CONNECTED
)) {
454 /* Abort connect was not successful. */
455 if (__redisAsyncHandleConnect(ac
) != REDIS_OK
)
457 /* Try again later when the context is still not connected. */
458 if (!(c
->flags
& REDIS_CONNECTED
))
462 if (redisBufferRead(c
) == REDIS_ERR
) {
463 __redisAsyncDisconnect(ac
);
465 /* Always re-schedule reads */
467 redisProcessCallbacks(ac
);
471 void redisAsyncHandleWrite(redisAsyncContext
*ac
) {
472 redisContext
*c
= &(ac
->c
);
475 if (!(c
->flags
& REDIS_CONNECTED
)) {
476 /* Abort connect was not successful. */
477 if (__redisAsyncHandleConnect(ac
) != REDIS_OK
)
479 /* Try again later when the context is still not connected. */
480 if (!(c
->flags
& REDIS_CONNECTED
))
484 if (redisBufferWrite(c
,&done
) == REDIS_ERR
) {
485 __redisAsyncDisconnect(ac
);
487 /* Continue writing when not done, stop writing otherwise */
493 /* Always schedule reads after writes */
498 /* Sets a pointer to the first argument and its length starting at p. Returns
499 * the number of bytes to skip to get to the following argument. */
500 static char *nextArgument(char *start
, char **str
, size_t *len
) {
504 if (p
== NULL
) return NULL
;
507 *len
= (int)strtol(p
+1,NULL
,10);
514 /* Helper function for the redisAsyncCommand* family of functions. Writes a
515 * formatted command to the output buffer and registers the provided callback
516 * function with the context. */
517 static int __redisAsyncCommand(redisAsyncContext
*ac
, redisCallbackFn
*fn
, void *privdata
, char *cmd
, size_t len
) {
518 redisContext
*c
= &(ac
->c
);
520 int pvariant
, hasnext
;
526 /* Don't accept new commands when the connection is about to be closed. */
527 if (c
->flags
& (REDIS_DISCONNECTING
| REDIS_FREEING
)) return REDIS_ERR
;
531 cb
.privdata
= privdata
;
533 /* Find out which command will be appended. */
534 p
= nextArgument(cmd
,&cstr
,&clen
);
536 hasnext
= (p
[0] == '$');
537 pvariant
= (tolower(cstr
[0]) == 'p') ? 1 : 0;
541 if (hasnext
&& strncasecmp(cstr
,"subscribe\r\n",11) == 0) {
542 c
->flags
|= REDIS_SUBSCRIBED
;
544 /* Add every channel/pattern to the list of subscription callbacks. */
545 while ((p
= nextArgument(p
,&astr
,&alen
)) != NULL
) {
546 sname
= sdsnewlen(astr
,alen
);
548 dictReplace(ac
->sub
.patterns
,sname
,&cb
);
550 dictReplace(ac
->sub
.channels
,sname
,&cb
);
552 } else if (strncasecmp(cstr
,"unsubscribe\r\n",13) == 0) {
553 /* It is only useful to call (P)UNSUBSCRIBE when the context is
554 * subscribed to one or more channels or patterns. */
555 if (!(c
->flags
& REDIS_SUBSCRIBED
)) return REDIS_ERR
;
557 /* (P)UNSUBSCRIBE does not have its own response: every channel or
558 * pattern that is unsubscribed will receive a message. This means we
559 * should not append a callback function for this command. */
561 if (c
->flags
& REDIS_SUBSCRIBED
)
562 /* This will likely result in an error reply, but it needs to be
563 * received and passed to the callback. */
564 __redisPushCallback(&ac
->sub
.invalid
,&cb
);
566 __redisPushCallback(&ac
->replies
,&cb
);
569 __redisAppendCommand(c
,cmd
,len
);
571 /* Always schedule a write when the write buffer is non-empty */
577 int redisvAsyncCommand(redisAsyncContext
*ac
, redisCallbackFn
*fn
, void *privdata
, const char *format
, va_list ap
) {
581 len
= redisvFormatCommand(&cmd
,format
,ap
);
582 status
= __redisAsyncCommand(ac
,fn
,privdata
,cmd
,len
);
587 int redisAsyncCommand(redisAsyncContext
*ac
, redisCallbackFn
*fn
, void *privdata
, const char *format
, ...) {
591 status
= redisvAsyncCommand(ac
,fn
,privdata
,format
,ap
);
596 int redisAsyncCommandArgv(redisAsyncContext
*ac
, redisCallbackFn
*fn
, void *privdata
, int argc
, const char **argv
, const size_t *argvlen
) {
600 len
= redisFormatCommandArgv(&cmd
,argc
,argv
,argvlen
);
601 status
= __redisAsyncCommand(ac
,fn
,privdata
,cmd
,len
);