2 * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
8 * * Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * * Neither the name of Redis nor the names of its contributors may be used
14 * to endorse or promote products derived from this software without
15 * specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
41 typedef struct redisReader
{
42 struct redisReplyObjectFunctions
*fn
;
43 sds error
; /* holds optional error */
44 void *reply
; /* holds temporary reply */
46 sds buf
; /* read buffer */
47 unsigned int pos
; /* buffer cursor */
49 redisReadTask rstack
[3]; /* stack of read tasks */
50 int ridx
; /* index of stack */
53 static redisReply
*createReplyObject(int type
);
54 static void *createStringObject(const redisReadTask
*task
, char *str
, size_t len
);
55 static void *createArrayObject(const redisReadTask
*task
, int elements
);
56 static void *createIntegerObject(const redisReadTask
*task
, long long value
);
57 static void *createNilObject(const redisReadTask
*task
);
58 static void redisSetReplyReaderError(redisReader
*r
, sds err
);
60 /* Default set of functions to build the reply. */
61 static redisReplyObjectFunctions defaultFunctions
= {
69 /* Create a reply object */
70 static redisReply
*createReplyObject(int type
) {
71 redisReply
*r
= calloc(sizeof(*r
),1);
78 /* Free a reply object */
79 void freeReplyObject(void *reply
) {
80 redisReply
*r
= reply
;
84 case REDIS_REPLY_INTEGER
:
85 break; /* Nothing to free */
86 case REDIS_REPLY_ARRAY
:
87 for (j
= 0; j
< r
->elements
; j
++)
88 if (r
->element
[j
]) freeReplyObject(r
->element
[j
]);
99 static void *createStringObject(const redisReadTask
*task
, char *str
, size_t len
) {
100 redisReply
*r
= createReplyObject(task
->type
);
101 char *value
= malloc(len
+1);
102 if (!value
) redisOOM();
103 assert(task
->type
== REDIS_REPLY_ERROR
||
104 task
->type
== REDIS_REPLY_STATUS
||
105 task
->type
== REDIS_REPLY_STRING
);
107 /* Copy string value */
108 memcpy(value
,str
,len
);
114 redisReply
*parent
= task
->parent
;
115 assert(parent
->type
== REDIS_REPLY_ARRAY
);
116 parent
->element
[task
->idx
] = r
;
121 static void *createArrayObject(const redisReadTask
*task
, int elements
) {
122 redisReply
*r
= createReplyObject(REDIS_REPLY_ARRAY
);
123 r
->elements
= elements
;
124 if ((r
->element
= calloc(sizeof(redisReply
*),elements
)) == NULL
)
127 redisReply
*parent
= task
->parent
;
128 assert(parent
->type
== REDIS_REPLY_ARRAY
);
129 parent
->element
[task
->idx
] = r
;
134 static void *createIntegerObject(const redisReadTask
*task
, long long value
) {
135 redisReply
*r
= createReplyObject(REDIS_REPLY_INTEGER
);
138 redisReply
*parent
= task
->parent
;
139 assert(parent
->type
== REDIS_REPLY_ARRAY
);
140 parent
->element
[task
->idx
] = r
;
145 static void *createNilObject(const redisReadTask
*task
) {
146 redisReply
*r
= createReplyObject(REDIS_REPLY_NIL
);
148 redisReply
*parent
= task
->parent
;
149 assert(parent
->type
== REDIS_REPLY_ARRAY
);
150 parent
->element
[task
->idx
] = r
;
155 static char *readBytes(redisReader
*r
, unsigned int bytes
) {
157 if (sdslen(r
->buf
)-r
->pos
>= bytes
) {
165 static char *seekNewline(char *s
) {
166 /* Find pointer to \r\n without strstr */
181 static char *readLine(redisReader
*r
, int *_len
) {
188 len
= s
-(r
->buf
+r
->pos
);
189 r
->pos
+= len
+2; /* skip \r\n */
190 if (_len
) *_len
= len
;
196 static void moveToNextTask(redisReader
*r
) {
197 redisReadTask
*cur
, *prv
;
198 while (r
->ridx
>= 0) {
199 /* Return a.s.a.p. when the stack is now empty. */
205 cur
= &(r
->rstack
[r
->ridx
]);
206 prv
= &(r
->rstack
[r
->ridx
-1]);
207 assert(prv
->type
== REDIS_REPLY_ARRAY
);
208 if (cur
->idx
== prv
->elements
-1) {
211 /* Reset the type because the next item can be anything */
212 assert(cur
->idx
< prv
->elements
);
221 static int processLineItem(redisReader
*r
) {
222 redisReadTask
*cur
= &(r
->rstack
[r
->ridx
]);
227 if ((p
= readLine(r
,&len
)) != NULL
) {
229 if (cur
->type
== REDIS_REPLY_INTEGER
) {
230 obj
= r
->fn
->createInteger(cur
,strtoll(p
,NULL
,10));
232 obj
= r
->fn
->createString(cur
,p
,len
);
235 obj
= (void*)(size_t)(cur
->type
);
238 /* If there is no root yet, register this object as root. */
239 if (r
->reply
== NULL
)
247 static int processBulkItem(redisReader
*r
) {
248 redisReadTask
*cur
= &(r
->rstack
[r
->ridx
]);
252 unsigned long bytelen
;
258 bytelen
= s
-(r
->buf
+r
->pos
)+2; /* include \r\n */
259 len
= strtol(p
,NULL
,10);
262 /* The nil object can always be created. */
263 obj
= r
->fn
? r
->fn
->createNil(cur
) :
264 (void*)REDIS_REPLY_NIL
;
266 /* Only continue when the buffer contains the entire bulk item. */
267 bytelen
+= len
+2; /* include \r\n */
268 if (r
->pos
+bytelen
<= sdslen(r
->buf
)) {
269 obj
= r
->fn
? r
->fn
->createString(cur
,s
+2,len
) :
270 (void*)REDIS_REPLY_STRING
;
274 /* Proceed when obj was created. */
277 if (r
->reply
== NULL
)
286 static int processMultiBulkItem(redisReader
*r
) {
287 redisReadTask
*cur
= &(r
->rstack
[r
->ridx
]);
292 if ((p
= readLine(r
,NULL
)) != NULL
) {
293 elements
= strtol(p
,NULL
,10);
294 if (elements
== -1) {
295 obj
= r
->fn
? r
->fn
->createNil(cur
) :
296 (void*)REDIS_REPLY_NIL
;
299 obj
= r
->fn
? r
->fn
->createArray(cur
,elements
) :
300 (void*)REDIS_REPLY_ARRAY
;
302 /* Modify task stack when there are more than 0 elements. */
304 cur
->elements
= elements
;
306 r
->rstack
[r
->ridx
].type
= -1;
307 r
->rstack
[r
->ridx
].elements
= -1;
308 r
->rstack
[r
->ridx
].parent
= obj
;
309 r
->rstack
[r
->ridx
].idx
= 0;
315 /* Object was created, so we can always continue. */
316 if (r
->reply
== NULL
)
323 static int processItem(redisReader
*r
) {
324 redisReadTask
*cur
= &(r
->rstack
[r
->ridx
]);
328 /* check if we need to read type */
330 if ((p
= readBytes(r
,1)) != NULL
) {
333 cur
->type
= REDIS_REPLY_ERROR
;
336 cur
->type
= REDIS_REPLY_STATUS
;
339 cur
->type
= REDIS_REPLY_INTEGER
;
342 cur
->type
= REDIS_REPLY_STRING
;
345 cur
->type
= REDIS_REPLY_ARRAY
;
348 byte
= sdscatrepr(sdsempty(),p
,1);
349 redisSetReplyReaderError(r
,sdscatprintf(sdsempty(),
350 "protocol error, got %s as reply type byte", byte
));
355 /* could not consume 1 byte */
360 /* process typed item */
362 case REDIS_REPLY_ERROR
:
363 case REDIS_REPLY_STATUS
:
364 case REDIS_REPLY_INTEGER
:
365 return processLineItem(r
);
366 case REDIS_REPLY_STRING
:
367 return processBulkItem(r
);
368 case REDIS_REPLY_ARRAY
:
369 return processMultiBulkItem(r
);
371 redisSetReplyReaderError(r
,sdscatprintf(sdsempty(),
372 "unknown item type '%d'", cur
->type
));
377 void *redisReplyReaderCreate() {
378 redisReader
*r
= calloc(sizeof(redisReader
),1);
380 r
->fn
= &defaultFunctions
;
386 /* Set the function set to build the reply. Returns REDIS_OK when there
387 * is no temporary object and it can be set, REDIS_ERR otherwise. */
388 int redisReplyReaderSetReplyObjectFunctions(void *reader
, redisReplyObjectFunctions
*fn
) {
389 redisReader
*r
= reader
;
390 if (r
->reply
== NULL
) {
397 /* External libraries wrapping hiredis might need access to the temporary
398 * variable while the reply is built up. When the reader contains an
399 * object in between receiving some bytes to parse, this object might
400 * otherwise be free'd by garbage collection. */
401 void *redisReplyReaderGetObject(void *reader
) {
402 redisReader
*r
= reader
;
406 void redisReplyReaderFree(void *reader
) {
407 redisReader
*r
= reader
;
408 if (r
->error
!= NULL
)
410 if (r
->reply
!= NULL
&& r
->fn
)
411 r
->fn
->freeObject(r
->reply
);
417 static void redisSetReplyReaderError(redisReader
*r
, sds err
) {
418 if (r
->reply
!= NULL
)
419 r
->fn
->freeObject(r
->reply
);
421 /* Clear remaining buffer when we see a protocol error. */
422 if (r
->buf
!= NULL
) {
431 char *redisReplyReaderGetError(void *reader
) {
432 redisReader
*r
= reader
;
436 void redisReplyReaderFeed(void *reader
, char *buf
, size_t len
) {
437 redisReader
*r
= reader
;
439 /* Copy the provided buffer. */
440 if (buf
!= NULL
&& len
>= 1)
441 r
->buf
= sdscatlen(r
->buf
,buf
,len
);
444 int redisReplyReaderGetReply(void *reader
, void **reply
) {
445 redisReader
*r
= reader
;
446 if (reply
!= NULL
) *reply
= NULL
;
448 /* When the buffer is empty, there will never be a reply. */
449 if (sdslen(r
->buf
) == 0)
452 /* Set first item to process when the stack is empty. */
454 r
->rstack
[0].type
= -1;
455 r
->rstack
[0].elements
= -1;
456 r
->rstack
[0].parent
= NULL
;
457 r
->rstack
[0].idx
= -1;
461 /* Process items in reply. */
463 if (processItem(r
) < 0)
466 /* Discard the consumed part of the buffer. */
468 if (r
->pos
== sdslen(r
->buf
)) {
469 /* sdsrange has a quirck on this edge case. */
473 r
->buf
= sdsrange(r
->buf
,r
->pos
,sdslen(r
->buf
));
478 /* Emit a reply when there is one. */
480 void *aux
= r
->reply
;
483 /* Destroy the buffer when it is empty and is quite large. */
484 if (sdslen(r
->buf
) == 0 && sdsavail(r
->buf
) > 16*1024) {
490 /* Check if there actually *is* a reply. */
491 if (r
->error
!= NULL
) {
494 if (reply
!= NULL
) *reply
= aux
;
500 /* Calculate the number of bytes needed to represent an integer as string. */
501 static int intlen(int i
) {
514 /* Helper function for redisvFormatCommand(). */
515 static void addArgument(sds a
, char ***argv
, int *argc
, int *totlen
) {
517 if ((*argv
= realloc(*argv
, sizeof(char*)*(*argc
))) == NULL
) redisOOM();
518 if (totlen
) *totlen
= *totlen
+1+intlen(sdslen(a
))+2+sdslen(a
)+2;
519 (*argv
)[(*argc
)-1] = a
;
522 int redisvFormatCommand(char **target
, const char *format
, va_list ap
) {
524 const char *arg
, *c
= format
;
525 char *cmd
= NULL
; /* final command */
526 int pos
; /* position in final command */
527 sds current
; /* current argument */
532 /* Abort if there is not target to set */
536 /* Build the command string accordingly to protocol */
537 current
= sdsempty();
539 if (*c
!= '%' || c
[1] == '\0') {
541 if (sdslen(current
) != 0) {
542 addArgument(current
, &argv
, &argc
, &totlen
);
543 current
= sdsempty();
546 current
= sdscatlen(current
,c
,1);
551 arg
= va_arg(ap
,char*);
552 current
= sdscat(current
,arg
);
555 arg
= va_arg(ap
,char*);
556 size
= va_arg(ap
,size_t);
557 current
= sdscatlen(current
,arg
,size
);
560 cmd
= sdscat(cmd
,"%");
568 /* Add the last argument if needed */
569 if (sdslen(current
) != 0) {
570 addArgument(current
, &argv
, &argc
, &totlen
);
575 /* Add bytes needed to hold multi bulk count */
576 totlen
+= 1+intlen(argc
)+2;
578 /* Build the command at protocol level */
579 cmd
= malloc(totlen
+1);
580 if (!cmd
) redisOOM();
581 pos
= sprintf(cmd
,"*%d\r\n",argc
);
582 for (j
= 0; j
< argc
; j
++) {
583 pos
+= sprintf(cmd
+pos
,"$%zu\r\n",sdslen(argv
[j
]));
584 memcpy(cmd
+pos
,argv
[j
],sdslen(argv
[j
]));
585 pos
+= sdslen(argv
[j
]);
590 assert(pos
== totlen
);
597 /* Format a command according to the Redis protocol. This function
598 * takes a format similar to printf:
600 * %s represents a C null terminated string you want to interpolate
601 * %b represents a binary safe string
603 * When using %b you need to provide both the pointer to the string
604 * and the length in bytes. Examples:
606 * len = redisFormatCommand(target, "GET %s", mykey);
607 * len = redisFormatCommand(target, "SET %s %b", mykey, myval, myvallen);
609 int redisFormatCommand(char **target
, const char *format
, ...) {
613 len
= redisvFormatCommand(target
,format
,ap
);
618 /* Format a command according to the Redis protocol. This function takes the
619 * number of arguments, an array with arguments and an array with their
620 * lengths. If the latter is set to NULL, strlen will be used to compute the
623 int redisFormatCommandArgv(char **target
, int argc
, const char **argv
, const size_t *argvlen
) {
624 char *cmd
= NULL
; /* final command */
625 int pos
; /* position in final command */
629 /* Calculate number of bytes needed for the command */
630 totlen
= 1+intlen(argc
)+2;
631 for (j
= 0; j
< argc
; j
++) {
632 len
= argvlen
? argvlen
[j
] : strlen(argv
[j
]);
633 totlen
+= 1+intlen(len
)+2+len
+2;
636 /* Build the command at protocol level */
637 cmd
= malloc(totlen
+1);
638 if (!cmd
) redisOOM();
639 pos
= sprintf(cmd
,"*%d\r\n",argc
);
640 for (j
= 0; j
< argc
; j
++) {
641 len
= argvlen
? argvlen
[j
] : strlen(argv
[j
]);
642 pos
+= sprintf(cmd
+pos
,"$%zu\r\n",len
);
643 memcpy(cmd
+pos
,argv
[j
],len
);
648 assert(pos
== totlen
);
654 void __redisSetError(redisContext
*c
, int type
, const sds errstr
) {
656 if (errstr
!= NULL
) {
659 /* Only REDIS_ERR_IO may lack a description! */
660 assert(type
== REDIS_ERR_IO
);
661 c
->errstr
= sdsnew(strerror(errno
));
665 static redisContext
*redisContextInit() {
666 redisContext
*c
= calloc(sizeof(redisContext
),1);
669 c
->obuf
= sdsempty();
670 c
->fn
= &defaultFunctions
;
675 void redisFree(redisContext
*c
) {
676 /* Disconnect before free'ing if not yet disconnected. */
677 if (c
->flags
& REDIS_CONNECTED
)
679 if (c
->errstr
!= NULL
)
683 if (c
->reader
!= NULL
)
684 redisReplyReaderFree(c
->reader
);
688 /* Connect to a Redis instance. On error the field error in the returned
689 * context will be set to the return value of the error function.
690 * When no set of reply functions is given, the default set will be used. */
691 redisContext
*redisConnect(const char *ip
, int port
) {
692 redisContext
*c
= redisContextInit();
693 c
->flags
|= REDIS_BLOCK
;
694 c
->flags
|= REDIS_CONNECTED
;
695 redisContextConnectTcp(c
,ip
,port
);
699 redisContext
*redisConnectNonBlock(const char *ip
, int port
) {
700 redisContext
*c
= redisContextInit();
701 c
->flags
&= ~REDIS_BLOCK
;
702 c
->flags
|= REDIS_CONNECTED
;
703 redisContextConnectTcp(c
,ip
,port
);
707 redisContext
*redisConnectUnix(const char *path
) {
708 redisContext
*c
= redisContextInit();
709 c
->flags
|= REDIS_BLOCK
;
710 c
->flags
|= REDIS_CONNECTED
;
711 redisContextConnectUnix(c
,path
);
715 redisContext
*redisConnectUnixNonBlock(const char *path
) {
716 redisContext
*c
= redisContextInit();
717 c
->flags
&= ~REDIS_BLOCK
;
718 c
->flags
|= REDIS_CONNECTED
;
719 redisContextConnectUnix(c
,path
);
723 /* Set the replyObjectFunctions to use. Returns REDIS_ERR when the reader
724 * was already initialized and the function set could not be re-set.
725 * Return REDIS_OK when they could be set. */
726 int redisSetReplyObjectFunctions(redisContext
*c
, redisReplyObjectFunctions
*fn
) {
727 if (c
->reader
!= NULL
)
733 /* Helper function to lazily create a reply reader. */
734 static void __redisCreateReplyReader(redisContext
*c
) {
735 if (c
->reader
== NULL
) {
736 c
->reader
= redisReplyReaderCreate();
737 assert(redisReplyReaderSetReplyObjectFunctions(c
->reader
,c
->fn
) == REDIS_OK
);
741 /* Use this function to handle a read event on the descriptor. It will try
742 * and read some bytes from the socket and feed them to the reply parser.
744 * After this function is called, you may use redisContextReadReply to
745 * see if there is a reply available. */
746 int redisBufferRead(redisContext
*c
) {
748 int nread
= read(c
->fd
,buf
,sizeof(buf
));
750 if (errno
== EAGAIN
) {
751 /* Try again later */
753 __redisSetError(c
,REDIS_ERR_IO
,NULL
);
756 } else if (nread
== 0) {
757 __redisSetError(c
,REDIS_ERR_EOF
,
758 sdsnew("Server closed the connection"));
761 __redisCreateReplyReader(c
);
762 redisReplyReaderFeed(c
->reader
,buf
,nread
);
767 /* Write the output buffer to the socket.
769 * Returns REDIS_OK when the buffer is empty, or (a part of) the buffer was
770 * succesfully written to the socket. When the buffer is empty after the
771 * write operation, "wdone" is set to 1 (if given).
773 * Returns REDIS_ERR if an error occured trying to write and sets
774 * c->error to hold the appropriate error string.
776 int redisBufferWrite(redisContext
*c
, int *done
) {
778 if (sdslen(c
->obuf
) > 0) {
779 nwritten
= write(c
->fd
,c
->obuf
,sdslen(c
->obuf
));
780 if (nwritten
== -1) {
781 if (errno
== EAGAIN
) {
782 /* Try again later */
784 __redisSetError(c
,REDIS_ERR_IO
,NULL
);
787 } else if (nwritten
> 0) {
788 if (nwritten
== (signed)sdslen(c
->obuf
)) {
790 c
->obuf
= sdsempty();
792 c
->obuf
= sdsrange(c
->obuf
,nwritten
,-1);
796 if (done
!= NULL
) *done
= (sdslen(c
->obuf
) == 0);
800 /* Internal helper function to try and get a reply from the reader,
801 * or set an error in the context otherwise. */
802 int redisGetReplyFromReader(redisContext
*c
, void **reply
) {
803 __redisCreateReplyReader(c
);
804 if (redisReplyReaderGetReply(c
->reader
,reply
) == REDIS_ERR
) {
805 __redisSetError(c
,REDIS_ERR_PROTOCOL
,
806 sdsnew(((redisReader
*)c
->reader
)->error
));
812 int redisGetReply(redisContext
*c
, void **reply
) {
816 /* Try to read pending replies */
817 if (redisGetReplyFromReader(c
,&aux
) == REDIS_ERR
)
820 /* For the blocking context, flush output buffer and read reply */
821 if (aux
== NULL
&& c
->flags
& REDIS_BLOCK
) {
822 /* Write until done */
824 if (redisBufferWrite(c
,&wdone
) == REDIS_ERR
)
828 /* Read until there is a reply */
830 if (redisBufferRead(c
) == REDIS_ERR
)
832 if (redisGetReplyFromReader(c
,&aux
) == REDIS_ERR
)
834 } while (aux
== NULL
);
837 /* Set reply object */
838 if (reply
!= NULL
) *reply
= aux
;
843 /* Helper function for the redisAppendCommand* family of functions.
845 * Write a formatted command to the output buffer. When this family
846 * is used, you need to call redisGetReply yourself to retrieve
847 * the reply (or replies in pub/sub).
849 void __redisAppendCommand(redisContext
*c
, char *cmd
, size_t len
) {
850 c
->obuf
= sdscatlen(c
->obuf
,cmd
,len
);
853 void redisvAppendCommand(redisContext
*c
, const char *format
, va_list ap
) {
856 len
= redisvFormatCommand(&cmd
,format
,ap
);
857 __redisAppendCommand(c
,cmd
,len
);
861 void redisAppendCommand(redisContext
*c
, const char *format
, ...) {
864 redisvAppendCommand(c
,format
,ap
);
868 void redisAppendCommandArgv(redisContext
*c
, int argc
, const char **argv
, const size_t *argvlen
) {
871 len
= redisFormatCommandArgv(&cmd
,argc
,argv
,argvlen
);
872 __redisAppendCommand(c
,cmd
,len
);
876 /* Helper function for the redisCommand* family of functions.
878 * Write a formatted command to the output buffer. If the given context is
879 * blocking, immediately read the reply into the "reply" pointer. When the
880 * context is non-blocking, the "reply" pointer will not be used and the
881 * command is simply appended to the write buffer.
883 * Returns the reply when a reply was succesfully retrieved. Returns NULL
884 * otherwise. When NULL is returned in a blocking context, the error field
885 * in the context will be set.
887 static void *__redisCommand(redisContext
*c
, char *cmd
, size_t len
) {
889 __redisAppendCommand(c
,cmd
,len
);
891 if (c
->flags
& REDIS_BLOCK
) {
892 if (redisGetReply(c
,&aux
) == REDIS_OK
)
899 void *redisvCommand(redisContext
*c
, const char *format
, va_list ap
) {
903 len
= redisvFormatCommand(&cmd
,format
,ap
);
904 reply
= __redisCommand(c
,cmd
,len
);
909 void *redisCommand(redisContext
*c
, const char *format
, ...) {
913 reply
= redisvCommand(c
,format
,ap
);
918 void *redisCommandArgv(redisContext
*c
, int argc
, const char **argv
, const size_t *argvlen
) {
922 len
= redisFormatCommandArgv(&cmd
,argc
,argv
,argvlen
);
923 reply
= __redisCommand(c
,cmd
,len
);