xref: /redis-3.2.3/deps/hiredis/async.c (revision c806dd79)
1 /*
2  * Copyright (c) 2009-2011, Salvatore Sanfilippo <antirez at gmail dot com>
3  * Copyright (c) 2010-2011, Pieter Noordhuis <pcnoordhuis at gmail dot com>
4  *
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions are met:
9  *
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.
18  *
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.
30  */
31 
32 #include "fmacros.h"
33 #include <stdlib.h>
34 #include <string.h>
35 #include <strings.h>
36 #include <assert.h>
37 #include <ctype.h>
38 #include <errno.h>
39 #include "async.h"
40 #include "net.h"
41 #include "dict.c"
42 #include "sds.h"
43 
44 #define _EL_ADD_READ(ctx) do { \
45         if ((ctx)->ev.addRead) (ctx)->ev.addRead((ctx)->ev.data); \
46     } while(0)
47 #define _EL_DEL_READ(ctx) do { \
48         if ((ctx)->ev.delRead) (ctx)->ev.delRead((ctx)->ev.data); \
49     } while(0)
50 #define _EL_ADD_WRITE(ctx) do { \
51         if ((ctx)->ev.addWrite) (ctx)->ev.addWrite((ctx)->ev.data); \
52     } while(0)
53 #define _EL_DEL_WRITE(ctx) do { \
54         if ((ctx)->ev.delWrite) (ctx)->ev.delWrite((ctx)->ev.data); \
55     } while(0)
56 #define _EL_CLEANUP(ctx) do { \
57         if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
58     } while(0);
59 
60 /* Forward declaration of function in hiredis.c */
61 void __redisAppendCommand(redisContext *c, char *cmd, size_t len);
62 
63 /* Functions managing dictionary of callbacks for pub/sub. */
callbackHash(const void * key)64 static unsigned int callbackHash(const void *key) {
65     return dictGenHashFunction((const unsigned char *)key,
66                                sdslen((const sds)key));
67 }
68 
callbackValDup(void * privdata,const void * src)69 static void *callbackValDup(void *privdata, const void *src) {
70     ((void) privdata);
71     redisCallback *dup = malloc(sizeof(*dup));
72     memcpy(dup,src,sizeof(*dup));
73     return dup;
74 }
75 
callbackKeyCompare(void * privdata,const void * key1,const void * key2)76 static int callbackKeyCompare(void *privdata, const void *key1, const void *key2) {
77     int l1, l2;
78     ((void) privdata);
79 
80     l1 = sdslen((const sds)key1);
81     l2 = sdslen((const sds)key2);
82     if (l1 != l2) return 0;
83     return memcmp(key1,key2,l1) == 0;
84 }
85 
callbackKeyDestructor(void * privdata,void * key)86 static void callbackKeyDestructor(void *privdata, void *key) {
87     ((void) privdata);
88     sdsfree((sds)key);
89 }
90 
callbackValDestructor(void * privdata,void * val)91 static void callbackValDestructor(void *privdata, void *val) {
92     ((void) privdata);
93     free(val);
94 }
95 
96 static dictType callbackDict = {
97     callbackHash,
98     NULL,
99     callbackValDup,
100     callbackKeyCompare,
101     callbackKeyDestructor,
102     callbackValDestructor
103 };
104 
redisAsyncInitialize(redisContext * c)105 static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
106     redisAsyncContext *ac;
107 
108     ac = realloc(c,sizeof(redisAsyncContext));
109     if (ac == NULL)
110         return NULL;
111 
112     c = &(ac->c);
113 
114     /* The regular connect functions will always set the flag REDIS_CONNECTED.
115      * For the async API, we want to wait until the first write event is
116      * received up before setting this flag, so reset it here. */
117     c->flags &= ~REDIS_CONNECTED;
118 
119     ac->err = 0;
120     ac->errstr = NULL;
121     ac->data = NULL;
122 
123     ac->ev.data = NULL;
124     ac->ev.addRead = NULL;
125     ac->ev.delRead = NULL;
126     ac->ev.addWrite = NULL;
127     ac->ev.delWrite = NULL;
128     ac->ev.cleanup = NULL;
129 
130     ac->onConnect = NULL;
131     ac->onDisconnect = NULL;
132 
133     ac->replies.head = NULL;
134     ac->replies.tail = NULL;
135     ac->sub.invalid.head = NULL;
136     ac->sub.invalid.tail = NULL;
137     ac->sub.channels = dictCreate(&callbackDict,NULL);
138     ac->sub.patterns = dictCreate(&callbackDict,NULL);
139     return ac;
140 }
141 
142 /* We want the error field to be accessible directly instead of requiring
143  * an indirection to the redisContext struct. */
__redisAsyncCopyError(redisAsyncContext * ac)144 static void __redisAsyncCopyError(redisAsyncContext *ac) {
145     redisContext *c = &(ac->c);
146     ac->err = c->err;
147     ac->errstr = c->errstr;
148 }
149 
redisAsyncConnect(const char * ip,int port)150 redisAsyncContext *redisAsyncConnect(const char *ip, int port) {
151     redisContext *c;
152     redisAsyncContext *ac;
153 
154     c = redisConnectNonBlock(ip,port);
155     if (c == NULL)
156         return NULL;
157 
158     ac = redisAsyncInitialize(c);
159     if (ac == NULL) {
160         redisFree(c);
161         return NULL;
162     }
163 
164     __redisAsyncCopyError(ac);
165     return ac;
166 }
167 
redisAsyncConnectBind(const char * ip,int port,const char * source_addr)168 redisAsyncContext *redisAsyncConnectBind(const char *ip, int port,
169                                          const char *source_addr) {
170     redisContext *c = redisConnectBindNonBlock(ip,port,source_addr);
171     redisAsyncContext *ac = redisAsyncInitialize(c);
172     __redisAsyncCopyError(ac);
173     return ac;
174 }
175 
redisAsyncConnectUnix(const char * path)176 redisAsyncContext *redisAsyncConnectUnix(const char *path) {
177     redisContext *c;
178     redisAsyncContext *ac;
179 
180     c = redisConnectUnixNonBlock(path);
181     if (c == NULL)
182         return NULL;
183 
184     ac = redisAsyncInitialize(c);
185     if (ac == NULL) {
186         redisFree(c);
187         return NULL;
188     }
189 
190     __redisAsyncCopyError(ac);
191     return ac;
192 }
193 
redisAsyncSetConnectCallback(redisAsyncContext * ac,redisConnectCallback * fn)194 int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn) {
195     if (ac->onConnect == NULL) {
196         ac->onConnect = fn;
197 
198         /* The common way to detect an established connection is to wait for
199          * the first write event to be fired. This assumes the related event
200          * library functions are already set. */
201         _EL_ADD_WRITE(ac);
202         return REDIS_OK;
203     }
204     return REDIS_ERR;
205 }
206 
redisAsyncSetDisconnectCallback(redisAsyncContext * ac,redisDisconnectCallback * fn)207 int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn) {
208     if (ac->onDisconnect == NULL) {
209         ac->onDisconnect = fn;
210         return REDIS_OK;
211     }
212     return REDIS_ERR;
213 }
214 
215 /* Helper functions to push/shift callbacks */
__redisPushCallback(redisCallbackList * list,redisCallback * source)216 static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
217     redisCallback *cb;
218 
219     /* Copy callback from stack to heap */
220     cb = malloc(sizeof(*cb));
221     if (cb == NULL)
222         return REDIS_ERR_OOM;
223 
224     if (source != NULL) {
225         memcpy(cb,source,sizeof(*cb));
226         cb->next = NULL;
227     }
228 
229     /* Store callback in list */
230     if (list->head == NULL)
231         list->head = cb;
232     if (list->tail != NULL)
233         list->tail->next = cb;
234     list->tail = cb;
235     return REDIS_OK;
236 }
237 
__redisShiftCallback(redisCallbackList * list,redisCallback * target)238 static int __redisShiftCallback(redisCallbackList *list, redisCallback *target) {
239     redisCallback *cb = list->head;
240     if (cb != NULL) {
241         list->head = cb->next;
242         if (cb == list->tail)
243             list->tail = NULL;
244 
245         /* Copy callback from heap to stack */
246         if (target != NULL)
247             memcpy(target,cb,sizeof(*cb));
248         free(cb);
249         return REDIS_OK;
250     }
251     return REDIS_ERR;
252 }
253 
__redisRunCallback(redisAsyncContext * ac,redisCallback * cb,redisReply * reply)254 static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisReply *reply) {
255     redisContext *c = &(ac->c);
256     if (cb->fn != NULL) {
257         c->flags |= REDIS_IN_CALLBACK;
258         cb->fn(ac,reply,cb->privdata);
259         c->flags &= ~REDIS_IN_CALLBACK;
260     }
261 }
262 
263 /* Helper function to free the context. */
__redisAsyncFree(redisAsyncContext * ac)264 static void __redisAsyncFree(redisAsyncContext *ac) {
265     redisContext *c = &(ac->c);
266     redisCallback cb;
267     dictIterator *it;
268     dictEntry *de;
269 
270     /* Execute pending callbacks with NULL reply. */
271     while (__redisShiftCallback(&ac->replies,&cb) == REDIS_OK)
272         __redisRunCallback(ac,&cb,NULL);
273 
274     /* Execute callbacks for invalid commands */
275     while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
276         __redisRunCallback(ac,&cb,NULL);
277 
278     /* Run subscription callbacks callbacks with NULL reply */
279     it = dictGetIterator(ac->sub.channels);
280     while ((de = dictNext(it)) != NULL)
281         __redisRunCallback(ac,dictGetEntryVal(de),NULL);
282     dictReleaseIterator(it);
283     dictRelease(ac->sub.channels);
284 
285     it = dictGetIterator(ac->sub.patterns);
286     while ((de = dictNext(it)) != NULL)
287         __redisRunCallback(ac,dictGetEntryVal(de),NULL);
288     dictReleaseIterator(it);
289     dictRelease(ac->sub.patterns);
290 
291     /* Signal event lib to clean up */
292     _EL_CLEANUP(ac);
293 
294     /* Execute disconnect callback. When redisAsyncFree() initiated destroying
295      * this context, the status will always be REDIS_OK. */
296     if (ac->onDisconnect && (c->flags & REDIS_CONNECTED)) {
297         if (c->flags & REDIS_FREEING) {
298             ac->onDisconnect(ac,REDIS_OK);
299         } else {
300             ac->onDisconnect(ac,(ac->err == 0) ? REDIS_OK : REDIS_ERR);
301         }
302     }
303 
304     /* Cleanup self */
305     redisFree(c);
306 }
307 
308 /* Free the async context. When this function is called from a callback,
309  * control needs to be returned to redisProcessCallbacks() before actual
310  * free'ing. To do so, a flag is set on the context which is picked up by
311  * redisProcessCallbacks(). Otherwise, the context is immediately free'd. */
redisAsyncFree(redisAsyncContext * ac)312 void redisAsyncFree(redisAsyncContext *ac) {
313     redisContext *c = &(ac->c);
314     c->flags |= REDIS_FREEING;
315     if (!(c->flags & REDIS_IN_CALLBACK))
316         __redisAsyncFree(ac);
317 }
318 
319 /* Helper function to make the disconnect happen and clean up. */
__redisAsyncDisconnect(redisAsyncContext * ac)320 static void __redisAsyncDisconnect(redisAsyncContext *ac) {
321     redisContext *c = &(ac->c);
322 
323     /* Make sure error is accessible if there is any */
324     __redisAsyncCopyError(ac);
325 
326     if (ac->err == 0) {
327         /* For clean disconnects, there should be no pending callbacks. */
328         assert(__redisShiftCallback(&ac->replies,NULL) == REDIS_ERR);
329     } else {
330         /* Disconnection is caused by an error, make sure that pending
331          * callbacks cannot call new commands. */
332         c->flags |= REDIS_DISCONNECTING;
333     }
334 
335     /* For non-clean disconnects, __redisAsyncFree() will execute pending
336      * callbacks with a NULL-reply. */
337     __redisAsyncFree(ac);
338 }
339 
340 /* Tries to do a clean disconnect from Redis, meaning it stops new commands
341  * from being issued, but tries to flush the output buffer and execute
342  * callbacks for all remaining replies. When this function is called from a
343  * callback, there might be more replies and we can safely defer disconnecting
344  * to redisProcessCallbacks(). Otherwise, we can only disconnect immediately
345  * when there are no pending callbacks. */
redisAsyncDisconnect(redisAsyncContext * ac)346 void redisAsyncDisconnect(redisAsyncContext *ac) {
347     redisContext *c = &(ac->c);
348     c->flags |= REDIS_DISCONNECTING;
349     if (!(c->flags & REDIS_IN_CALLBACK) && ac->replies.head == NULL)
350         __redisAsyncDisconnect(ac);
351 }
352 
__redisGetSubscribeCallback(redisAsyncContext * ac,redisReply * reply,redisCallback * dstcb)353 static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply, redisCallback *dstcb) {
354     redisContext *c = &(ac->c);
355     dict *callbacks;
356     dictEntry *de;
357     int pvariant;
358     char *stype;
359     sds sname;
360 
361     /* Custom reply functions are not supported for pub/sub. This will fail
362      * very hard when they are used... */
363     if (reply->type == REDIS_REPLY_ARRAY) {
364         assert(reply->elements >= 2);
365         assert(reply->element[0]->type == REDIS_REPLY_STRING);
366         stype = reply->element[0]->str;
367         pvariant = (tolower(stype[0]) == 'p') ? 1 : 0;
368 
369         if (pvariant)
370             callbacks = ac->sub.patterns;
371         else
372             callbacks = ac->sub.channels;
373 
374         /* Locate the right callback */
375         assert(reply->element[1]->type == REDIS_REPLY_STRING);
376         sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len);
377         de = dictFind(callbacks,sname);
378         if (de != NULL) {
379             memcpy(dstcb,dictGetEntryVal(de),sizeof(*dstcb));
380 
381             /* If this is an unsubscribe message, remove it. */
382             if (strcasecmp(stype+pvariant,"unsubscribe") == 0) {
383                 dictDelete(callbacks,sname);
384 
385                 /* If this was the last unsubscribe message, revert to
386                  * non-subscribe mode. */
387                 assert(reply->element[2]->type == REDIS_REPLY_INTEGER);
388                 if (reply->element[2]->integer == 0)
389                     c->flags &= ~REDIS_SUBSCRIBED;
390             }
391         }
392         sdsfree(sname);
393     } else {
394         /* Shift callback for invalid commands. */
395         __redisShiftCallback(&ac->sub.invalid,dstcb);
396     }
397     return REDIS_OK;
398 }
399 
redisProcessCallbacks(redisAsyncContext * ac)400 void redisProcessCallbacks(redisAsyncContext *ac) {
401     redisContext *c = &(ac->c);
402     redisCallback cb = {NULL, NULL, NULL};
403     void *reply = NULL;
404     int status;
405 
406     while((status = redisGetReply(c,&reply)) == REDIS_OK) {
407         if (reply == NULL) {
408             /* When the connection is being disconnected and there are
409              * no more replies, this is the cue to really disconnect. */
410             if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0) {
411                 __redisAsyncDisconnect(ac);
412                 return;
413             }
414 
415             /* If monitor mode, repush callback */
416             if(c->flags & REDIS_MONITORING) {
417                 __redisPushCallback(&ac->replies,&cb);
418             }
419 
420             /* When the connection is not being disconnected, simply stop
421              * trying to get replies and wait for the next loop tick. */
422             break;
423         }
424 
425         /* Even if the context is subscribed, pending regular callbacks will
426          * get a reply before pub/sub messages arrive. */
427         if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
428             /*
429              * A spontaneous reply in a not-subscribed context can be the error
430              * reply that is sent when a new connection exceeds the maximum
431              * number of allowed connections on the server side.
432              *
433              * This is seen as an error instead of a regular reply because the
434              * server closes the connection after sending it.
435              *
436              * To prevent the error from being overwritten by an EOF error the
437              * connection is closed here. See issue #43.
438              *
439              * Another possibility is that the server is loading its dataset.
440              * In this case we also want to close the connection, and have the
441              * user wait until the server is ready to take our request.
442              */
443             if (((redisReply*)reply)->type == REDIS_REPLY_ERROR) {
444                 c->err = REDIS_ERR_OTHER;
445                 snprintf(c->errstr,sizeof(c->errstr),"%s",((redisReply*)reply)->str);
446                 c->reader->fn->freeObject(reply);
447                 __redisAsyncDisconnect(ac);
448                 return;
449             }
450             /* No more regular callbacks and no errors, the context *must* be subscribed or monitoring. */
451             assert((c->flags & REDIS_SUBSCRIBED || c->flags & REDIS_MONITORING));
452             if(c->flags & REDIS_SUBSCRIBED)
453                 __redisGetSubscribeCallback(ac,reply,&cb);
454         }
455 
456         if (cb.fn != NULL) {
457             __redisRunCallback(ac,&cb,reply);
458             c->reader->fn->freeObject(reply);
459 
460             /* Proceed with free'ing when redisAsyncFree() was called. */
461             if (c->flags & REDIS_FREEING) {
462                 __redisAsyncFree(ac);
463                 return;
464             }
465         } else {
466             /* No callback for this reply. This can either be a NULL callback,
467              * or there were no callbacks to begin with. Either way, don't
468              * abort with an error, but simply ignore it because the client
469              * doesn't know what the server will spit out over the wire. */
470             c->reader->fn->freeObject(reply);
471         }
472     }
473 
474     /* Disconnect when there was an error reading the reply */
475     if (status != REDIS_OK)
476         __redisAsyncDisconnect(ac);
477 }
478 
479 /* Internal helper function to detect socket status the first time a read or
480  * write event fires. When connecting was not succesful, the connect callback
481  * is called with a REDIS_ERR status and the context is free'd. */
__redisAsyncHandleConnect(redisAsyncContext * ac)482 static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
483     redisContext *c = &(ac->c);
484 
485     if (redisCheckSocketError(c) == REDIS_ERR) {
486         /* Try again later when connect(2) is still in progress. */
487         if (errno == EINPROGRESS)
488             return REDIS_OK;
489 
490         if (ac->onConnect) ac->onConnect(ac,REDIS_ERR);
491         __redisAsyncDisconnect(ac);
492         return REDIS_ERR;
493     }
494 
495     /* Mark context as connected. */
496     c->flags |= REDIS_CONNECTED;
497     if (ac->onConnect) ac->onConnect(ac,REDIS_OK);
498     return REDIS_OK;
499 }
500 
501 /* This function should be called when the socket is readable.
502  * It processes all replies that can be read and executes their callbacks.
503  */
redisAsyncHandleRead(redisAsyncContext * ac)504 void redisAsyncHandleRead(redisAsyncContext *ac) {
505     redisContext *c = &(ac->c);
506 
507     if (!(c->flags & REDIS_CONNECTED)) {
508         /* Abort connect was not successful. */
509         if (__redisAsyncHandleConnect(ac) != REDIS_OK)
510             return;
511         /* Try again later when the context is still not connected. */
512         if (!(c->flags & REDIS_CONNECTED))
513             return;
514     }
515 
516     if (redisBufferRead(c) == REDIS_ERR) {
517         __redisAsyncDisconnect(ac);
518     } else {
519         /* Always re-schedule reads */
520         _EL_ADD_READ(ac);
521         redisProcessCallbacks(ac);
522     }
523 }
524 
redisAsyncHandleWrite(redisAsyncContext * ac)525 void redisAsyncHandleWrite(redisAsyncContext *ac) {
526     redisContext *c = &(ac->c);
527     int done = 0;
528 
529     if (!(c->flags & REDIS_CONNECTED)) {
530         /* Abort connect was not successful. */
531         if (__redisAsyncHandleConnect(ac) != REDIS_OK)
532             return;
533         /* Try again later when the context is still not connected. */
534         if (!(c->flags & REDIS_CONNECTED))
535             return;
536     }
537 
538     if (redisBufferWrite(c,&done) == REDIS_ERR) {
539         __redisAsyncDisconnect(ac);
540     } else {
541         /* Continue writing when not done, stop writing otherwise */
542         if (!done)
543             _EL_ADD_WRITE(ac);
544         else
545             _EL_DEL_WRITE(ac);
546 
547         /* Always schedule reads after writes */
548         _EL_ADD_READ(ac);
549     }
550 }
551 
552 /* Sets a pointer to the first argument and its length starting at p. Returns
553  * the number of bytes to skip to get to the following argument. */
nextArgument(char * start,char ** str,size_t * len)554 static char *nextArgument(char *start, char **str, size_t *len) {
555     char *p = start;
556     if (p[0] != '$') {
557         p = strchr(p,'$');
558         if (p == NULL) return NULL;
559     }
560 
561     *len = (int)strtol(p+1,NULL,10);
562     p = strchr(p,'\r');
563     assert(p);
564     *str = p+2;
565     return p+2+(*len)+2;
566 }
567 
568 /* Helper function for the redisAsyncCommand* family of functions. Writes a
569  * formatted command to the output buffer and registers the provided callback
570  * function with the context. */
__redisAsyncCommand(redisAsyncContext * ac,redisCallbackFn * fn,void * privdata,char * cmd,size_t len)571 static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, char *cmd, size_t len) {
572     redisContext *c = &(ac->c);
573     redisCallback cb;
574     int pvariant, hasnext;
575     char *cstr, *astr;
576     size_t clen, alen;
577     char *p;
578     sds sname;
579 
580     /* Don't accept new commands when the connection is about to be closed. */
581     if (c->flags & (REDIS_DISCONNECTING | REDIS_FREEING)) return REDIS_ERR;
582 
583     /* Setup callback */
584     cb.fn = fn;
585     cb.privdata = privdata;
586 
587     /* Find out which command will be appended. */
588     p = nextArgument(cmd,&cstr,&clen);
589     assert(p != NULL);
590     hasnext = (p[0] == '$');
591     pvariant = (tolower(cstr[0]) == 'p') ? 1 : 0;
592     cstr += pvariant;
593     clen -= pvariant;
594 
595     if (hasnext && strncasecmp(cstr,"subscribe\r\n",11) == 0) {
596         c->flags |= REDIS_SUBSCRIBED;
597 
598         /* Add every channel/pattern to the list of subscription callbacks. */
599         while ((p = nextArgument(p,&astr,&alen)) != NULL) {
600             sname = sdsnewlen(astr,alen);
601             if (pvariant)
602                 dictReplace(ac->sub.patterns,sname,&cb);
603             else
604                 dictReplace(ac->sub.channels,sname,&cb);
605         }
606     } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
607         /* It is only useful to call (P)UNSUBSCRIBE when the context is
608          * subscribed to one or more channels or patterns. */
609         if (!(c->flags & REDIS_SUBSCRIBED)) return REDIS_ERR;
610 
611         /* (P)UNSUBSCRIBE does not have its own response: every channel or
612          * pattern that is unsubscribed will receive a message. This means we
613          * should not append a callback function for this command. */
614      } else if(strncasecmp(cstr,"monitor\r\n",9) == 0) {
615          /* Set monitor flag and push callback */
616          c->flags |= REDIS_MONITORING;
617          __redisPushCallback(&ac->replies,&cb);
618     } else {
619         if (c->flags & REDIS_SUBSCRIBED)
620             /* This will likely result in an error reply, but it needs to be
621              * received and passed to the callback. */
622             __redisPushCallback(&ac->sub.invalid,&cb);
623         else
624             __redisPushCallback(&ac->replies,&cb);
625     }
626 
627     __redisAppendCommand(c,cmd,len);
628 
629     /* Always schedule a write when the write buffer is non-empty */
630     _EL_ADD_WRITE(ac);
631 
632     return REDIS_OK;
633 }
634 
redisvAsyncCommand(redisAsyncContext * ac,redisCallbackFn * fn,void * privdata,const char * format,va_list ap)635 int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
636     char *cmd;
637     int len;
638     int status;
639     len = redisvFormatCommand(&cmd,format,ap);
640     status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
641     free(cmd);
642     return status;
643 }
644 
redisAsyncCommand(redisAsyncContext * ac,redisCallbackFn * fn,void * privdata,const char * format,...)645 int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, ...) {
646     va_list ap;
647     int status;
648     va_start(ap,format);
649     status = redisvAsyncCommand(ac,fn,privdata,format,ap);
650     va_end(ap);
651     return status;
652 }
653 
redisAsyncCommandArgv(redisAsyncContext * ac,redisCallbackFn * fn,void * privdata,int argc,const char ** argv,const size_t * argvlen)654 int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
655     char *cmd;
656     int len;
657     int status;
658     len = redisFormatCommandArgv(&cmd,argc,argv,argvlen);
659     status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
660     free(cmd);
661     return status;
662 }
663