From 9f1ae9abee1da4e53b7e535e172213d81eb75ed7 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Wed, 13 Oct 2010 09:26:44 +0200 Subject: [PATCH 01/18] Allow to specify which specific test files to run --- src/Makefile | 2 +- tests/test_helper.tcl | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Makefile b/src/Makefile index e1e989c60..3add29256 100644 --- a/src/Makefile +++ b/src/Makefile @@ -126,7 +126,7 @@ dep: $(CC) -MM *.c test: - (cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}") + (cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}") bench: ./redis-benchmark diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index ee7fa3e19..f427fabce 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -16,6 +16,7 @@ set ::valgrind 0 set ::denytags {} set ::allowtags {} set ::external 0; # If "1" this means, we are running against external instance +set ::file ""; # If set, runs only the tests in this comma separated list proc execute_tests name { source "tests/$name.tcl" @@ -80,8 +81,7 @@ proc cleanup {} { catch {exec rm -rf {*}[glob tests/tmp/server.*]} } -proc main {} { - cleanup +proc execute_everything {} { execute_tests "unit/auth" execute_tests "unit/protocol" execute_tests "unit/basic" @@ -110,6 +110,18 @@ proc main {} { execute_tests "unit/expire" execute_tests "unit/other" execute_tests "unit/cas" +} + +proc main {} { + cleanup + + if {[string length $::file] > 0} { + foreach {file} [split $::file ,] { + execute_tests $file + } + } else { + execute_everything + } cleanup puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed" @@ -132,6 +144,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} { } } incr j + } elseif {$opt eq {--file}} { + set ::file $arg + incr j } elseif {$opt eq {--host}} { set ::external 1 set ::host $arg From 941c9fa2859cc4ce68911a1b5a8f88b53d802f77 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Wed, 13 Oct 2010 11:25:40 +0200 Subject: [PATCH 02/18] Return OK on QUIT --- src/networking.c | 7 +++++++ src/redis.c | 10 +++++++--- src/redis.h | 1 + tests/support/redis.tcl | 8 ++++++++ tests/support/server.tcl | 16 ++++++---------- tests/test_helper.tcl | 22 ++++++++++++++++++++++ 6 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/networking.c b/src/networking.c index 632fd0476..a1d8f5644 100644 --- a/src/networking.c +++ b/src/networking.c @@ -546,6 +546,9 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { if (listLength(c->reply) == 0) { c->sentlen = 0; aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); + + /* Close connection after entire reply has been sent. */ + if (c->flags & REDIS_QUIT) freeClient(c); } } @@ -675,6 +678,10 @@ again: * will try to reiterate. The following line will make it return asap. */ if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return; + /* Never continue to process the input buffer after QUIT. After the output + * buffer is flushed (with the OK), the connection will be dropped. */ + if (c->flags & REDIS_QUIT) return; + if (seeknewline && c->bulklen == -1) c->newline = strchr(c->querybuf,'\n'); seeknewline = 1; if (c->bulklen == -1) { diff --git a/src/redis.c b/src/redis.c index 27a855d97..a1ac2a150 100644 --- a/src/redis.c +++ b/src/redis.c @@ -962,10 +962,14 @@ int processCommand(redisClient *c) { } /* -- end of multi bulk commands processing -- */ - /* The QUIT command is handled as a special case. Normal command - * procs are unable to close the client connection safely */ + /* The QUIT command is handled separately. Normal command procs will + * go through checking for replication and QUIT will cause trouble + * when FORCE_REPLICATION is enabled and would be implemented in + * a regular command proc. */ + redisAssert(!(c->flags & REDIS_QUIT)); if (!strcasecmp(c->argv[0]->ptr,"quit")) { - freeClient(c); + c->flags |= REDIS_QUIT; + addReply(c,shared.ok); return 0; } diff --git a/src/redis.h b/src/redis.h index 3e9fc2369..e525a99b0 100644 --- a/src/redis.h +++ b/src/redis.h @@ -144,6 +144,7 @@ #define REDIS_BLOCKED 16 /* The client is waiting in a blocking operation */ #define REDIS_IO_WAIT 32 /* The client is waiting for Virtual Memory I/O */ #define REDIS_DIRTY_CAS 64 /* Watched keys modified. EXEC will fail. */ +#define REDIS_QUIT 128 /* Client will be disconnected after reply is sent */ /* Slave replication state - slave side */ #define REDIS_REPL_NONE 0 /* No active replication */ diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl index 7c7c65c65..98cf86f09 100644 --- a/tests/support/redis.tcl +++ b/tests/support/redis.tcl @@ -123,6 +123,14 @@ proc ::redis::__method__read {id fd} { ::redis::redis_read_reply $fd } +proc ::redis::__method__write {id fd buf} { + ::redis::redis_write $fd $buf +} + +proc ::redis::__method__flush {id fd} { + flush $fd +} + proc ::redis::__method__close {id fd} { catch {close $fd} catch {unset ::redis::fd($id)} diff --git a/tests/support/server.tcl b/tests/support/server.tcl index e5ca6c6cd..1507088e0 100644 --- a/tests/support/server.tcl +++ b/tests/support/server.tcl @@ -215,7 +215,8 @@ proc start_server {options {code undefined}} { if {[dict exists $config port]} { set port [dict get $config port] } # setup config dict - dict set srv "config" $config_file + dict set srv "config_file" $config_file + dict set srv "config" $config dict set srv "pid" $pid dict set srv "host" $host dict set srv "port" $port @@ -238,17 +239,12 @@ proc start_server {options {code undefined}} { after 10 } - set client [redis $host $port] - dict set srv "client" $client - - # select the right db when we don't have to authenticate - if {![dict exists $config requirepass]} { - $client select 9 - } - # append the server to the stack lappend ::servers $srv - + + # connect client (after server dict is put on the stack) + reconnect + # execute provided block set curnum $::testnum if {![catch { uplevel 1 $code } err]} { diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index f427fabce..a6c3e8335 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -50,6 +50,28 @@ proc r {args} { [srv $level "client"] {*}$args } +proc reconnect {args} { + set level [lindex $args 0] + if {[string length $level] == 0 || ![string is integer $level]} { + set level 0 + } + + set srv [lindex $::servers end+$level] + set host [dict get $srv "host"] + set port [dict get $srv "port"] + set config [dict get $srv "config"] + set client [redis $host $port] + dict set srv "client" $client + + # select the right db when we don't have to authenticate + if {![dict exists $config "requirepass"]} { + $client select 9 + } + + # re-set $srv in the servers list + set ::servers [lreplace $::servers end+$level 1 $srv] +} + proc redis_deferring_client {args} { set level 0 if {[llength $args] > 0 && [string is integer [lindex $args 0]]} { From 5a4f9f27e7982d2784c17dc053c8629e13c9d7e8 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 12:54:53 +0200 Subject: [PATCH 03/18] Add tests for OK on QUIT --- tests/test_helper.tcl | 1 + tests/unit/quit.tcl | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 tests/unit/quit.tcl diff --git a/tests/test_helper.tcl b/tests/test_helper.tcl index a6c3e8335..4c207f643 100644 --- a/tests/test_helper.tcl +++ b/tests/test_helper.tcl @@ -115,6 +115,7 @@ proc execute_everything {} { execute_tests "unit/expire" execute_tests "unit/other" execute_tests "unit/cas" + execute_tests "unit/quit" execute_tests "integration/replication" execute_tests "integration/aof" # execute_tests "integration/redis-cli" diff --git a/tests/unit/quit.tcl b/tests/unit/quit.tcl new file mode 100644 index 000000000..4cf440abf --- /dev/null +++ b/tests/unit/quit.tcl @@ -0,0 +1,40 @@ +start_server {tags {"quit"}} { + proc format_command {args} { + set cmd "*[llength $args]\r\n" + foreach a $args { + append cmd "$[string length $a]\r\n$a\r\n" + } + set _ $cmd + } + + test "QUIT returns OK" { + reconnect + assert_equal OK [r quit] + assert_error * {r ping} + } + + test "Pipelined commands after QUIT must not be executed" { + reconnect + r write [format_command quit] + r write [format_command set foo bar] + r flush + assert_equal OK [r read] + assert_error * {r read} + + reconnect + assert_equal {} [r get foo] + } + + test "Pipelined commands after QUIT that exceed read buffer size" { + reconnect + r write [format_command quit] + r write [format_command set foo [string repeat "x" 1024]] + r flush + assert_equal OK [r read] + assert_error * {r read} + + reconnect + assert_equal {} [r get foo] + + } +} From cd8788f26d06d8643828024537b8abe2b702759f Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 15:40:25 +0200 Subject: [PATCH 04/18] Refactor request parsing code for efficiency --- src/networking.c | 262 +++++++++++++++++++++++++++++------------------ src/redis.c | 127 ++--------------------- src/redis.h | 16 ++- 3 files changed, 181 insertions(+), 224 deletions(-) diff --git a/src/networking.c b/src/networking.c index a1d8f5644..cc4c9341d 100644 --- a/src/networking.c +++ b/src/networking.c @@ -28,13 +28,11 @@ redisClient *createClient(int fd) { selectDb(c,0); c->fd = fd; c->querybuf = sdsempty(); - c->newline = NULL; + c->reqtype = 0; c->argc = 0; c->argv = NULL; + c->multibulklen = 0; c->bulklen = -1; - c->multibulk = 0; - c->mbargc = 0; - c->mbargv = NULL; c->sentlen = 0; c->flags = 0; c->lastinteraction = time(NULL); @@ -374,13 +372,9 @@ void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { static void freeClientArgv(redisClient *c) { int j; - for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]); - for (j = 0; j < c->mbargc; j++) - decrRefCount(c->mbargv[j]); c->argc = 0; - c->mbargc = 0; } void freeClient(redisClient *c) { @@ -461,7 +455,6 @@ void freeClient(redisClient *c) { } /* Release memory */ zfree(c->argv); - zfree(c->mbargv); freeClientMultiState(c); zfree(c); } @@ -549,6 +542,7 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { /* Close connection after entire reply has been sent. */ if (c->flags & REDIS_QUIT) freeClient(c); + if (c->flags & REDIS_CLOSE_AFTER_REPLY) freeClient(c); } } @@ -633,9 +627,9 @@ void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask) /* resetClient prepare the client to process the next command */ void resetClient(redisClient *c) { freeClientArgv(c); + c->reqtype = 0; + c->multibulklen = 0; c->bulklen = -1; - c->multibulk = 0; - c->newline = NULL; } void closeTimedoutClients(void) { @@ -666,96 +660,170 @@ void closeTimedoutClients(void) { } } -void processInputBuffer(redisClient *c) { - int seeknewline = 0; +int processInlineBuffer(redisClient *c) { + char *newline = strstr(c->querybuf,"\r\n"); + int argc, j; + sds *argv; + size_t querylen; -again: - /* Before to process the input buffer, make sure the client is not - * waitig for a blocking operation such as BLPOP. Note that the first - * iteration the client is never blocked, otherwise the processInputBuffer - * would not be called at all, but after the execution of the first commands - * in the input buffer the client may be blocked, and the "goto again" - * will try to reiterate. The following line will make it return asap. */ - if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return; + /* Nothing to do without a \r\n */ + if (newline == NULL) + return REDIS_ERR; - /* Never continue to process the input buffer after QUIT. After the output - * buffer is flushed (with the OK), the connection will be dropped. */ - if (c->flags & REDIS_QUIT) return; + /* Split the input buffer up to the \r\n */ + querylen = newline-(c->querybuf); + argv = sdssplitlen(c->querybuf,querylen," ",1,&argc); - if (seeknewline && c->bulklen == -1) c->newline = strchr(c->querybuf,'\n'); - seeknewline = 1; - if (c->bulklen == -1) { - /* Read the first line of the query */ - size_t querylen; + /* Leave data after the first line of the query in the buffer */ + c->querybuf = sdsrange(c->querybuf,querylen+2,-1); - if (c->newline) { - char *p = c->newline; - sds query, *argv; - int argc, j; + /* Setup argv array on client structure */ + if (c->argv) zfree(c->argv); + c->argv = zmalloc(sizeof(robj*)*argc); - c->newline = NULL; - query = c->querybuf; - c->querybuf = sdsempty(); - querylen = 1+(p-(query)); - if (sdslen(query) > querylen) { - /* leave data after the first line of the query in the buffer */ - c->querybuf = sdscatlen(c->querybuf,query+querylen,sdslen(query)-querylen); - } - *p = '\0'; /* remove "\n" */ - if (*(p-1) == '\r') *(p-1) = '\0'; /* and "\r" if any */ - sdsupdatelen(query); - - /* Now we can split the query in arguments */ - argv = sdssplitlen(query,sdslen(query)," ",1,&argc); - sdsfree(query); - - if (c->argv) zfree(c->argv); - c->argv = zmalloc(sizeof(robj*)*argc); - - for (j = 0; j < argc; j++) { - if (sdslen(argv[j])) { - c->argv[c->argc] = createObject(REDIS_STRING,argv[j]); - c->argc++; - } else { - sdsfree(argv[j]); - } - } - zfree(argv); - if (c->argc) { - /* Execute the command. If the client is still valid - * after processCommand() return and there is something - * on the query buffer try to process the next command. */ - if (processCommand(c) && sdslen(c->querybuf)) goto again; - } else { - /* Nothing to process, argc == 0. Just process the query - * buffer if it's not empty or return to the caller */ - if (sdslen(c->querybuf)) goto again; - } - return; - } else if (sdslen(c->querybuf) >= REDIS_REQUEST_MAX_SIZE) { - redisLog(REDIS_VERBOSE, "Client protocol error"); - freeClient(c); - return; - } - } else { - /* Bulk read handling. Note that if we are at this point - the client already sent a command terminated with a newline, - we are reading the bulk data that is actually the last - argument of the command. */ - int qbl = sdslen(c->querybuf); - - if (c->bulklen <= qbl) { - /* Copy everything but the final CRLF as final argument */ - c->argv[c->argc] = createStringObject(c->querybuf,c->bulklen-2); + /* Create redis objects for all arguments. */ + for (c->argc = 0, j = 0; j < argc; j++) { + if (sdslen(argv[j])) { + c->argv[c->argc] = createObject(REDIS_STRING,argv[j]); c->argc++; - c->querybuf = sdsrange(c->querybuf,c->bulklen,-1); - /* Process the command. If the client is still valid after - * the processing and there is more data in the buffer - * try to parse it. */ - if (processCommand(c) && sdslen(c->querybuf)) goto again; - return; + } else { + sdsfree(argv[j]); } } + zfree(argv); + return REDIS_OK; +} + +/* Helper function. Trims query buffer to make the function that processes + * multi bulk requests idempotent. */ +static void setProtocolError(redisClient *c, int pos) { + c->flags |= REDIS_CLOSE_AFTER_REPLY; + c->querybuf = sdsrange(c->querybuf,pos,-1); +} + +int processMultibulkBuffer(redisClient *c) { + char *newline = NULL; + char *eptr; + int pos = 0, tolerr; + long bulklen; + + if (c->multibulklen == 0) { + /* The client should have been reset */ + redisAssert(c->argc == 0); + + /* Multi bulk length cannot be read without a \r\n */ + newline = strstr(c->querybuf,"\r\n"); + if (newline == NULL) + return REDIS_ERR; + + /* We know for sure there is a whole line since newline != NULL, + * so go ahead and find out the multi bulk length. */ + redisAssert(c->querybuf[0] == '*'); + c->multibulklen = strtol(c->querybuf+1,&eptr,10); + pos = (newline-c->querybuf)+2; + if (c->multibulklen <= 0) { + c->querybuf = sdsrange(c->querybuf,pos,-1); + return REDIS_OK; + } + + /* Setup argv array on client structure */ + if (c->argv) zfree(c->argv); + c->argv = zmalloc(sizeof(robj*)*c->multibulklen); + + /* Search new newline */ + newline = strstr(c->querybuf+pos,"\r\n"); + } + + redisAssert(c->multibulklen > 0); + while(c->multibulklen) { + /* Read bulk length if unknown */ + if (c->bulklen == -1) { + newline = strstr(c->querybuf+pos,"\r\n"); + if (newline != NULL) { + if (c->querybuf[pos] != '$') { + addReplyErrorFormat(c, + "Protocol error: expected '$', got '%c'", + c->querybuf[pos]); + setProtocolError(c,pos); + return REDIS_ERR; + } + + bulklen = strtol(c->querybuf+pos+1,&eptr,10); + tolerr = (eptr[0] != '\r'); + if (tolerr || bulklen == LONG_MIN || bulklen == LONG_MAX || + bulklen < 0 || bulklen > 1024*1024*1024) + { + addReplyError(c,"Protocol error: invalid bulk length"); + setProtocolError(c,pos); + return REDIS_ERR; + } + pos += eptr-(c->querybuf+pos)+2; + c->bulklen = bulklen; + } else { + /* No newline in current buffer, so wait for more data */ + break; + } + } + + /* Read bulk argument */ + if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) { + /* Not enough data (+2 == trailing \r\n) */ + break; + } else { + c->argv[c->argc++] = createStringObject(c->querybuf+pos,c->bulklen); + pos += c->bulklen+2; + c->bulklen = -1; + c->multibulklen--; + } + } + + /* Trim to pos */ + c->querybuf = sdsrange(c->querybuf,pos,-1); + + /* We're done when c->multibulk == 0 */ + if (c->multibulklen == 0) { + return REDIS_OK; + } + return REDIS_ERR; +} + +void processInputBuffer(redisClient *c) { + /* Keep processing while there is something in the input buffer */ + while(sdslen(c->querybuf)) { + /* Before to process the input buffer, make sure the client is not + * waitig for a blocking operation such as BLPOP. Note that the first + * iteration the client is never blocked, otherwise the processInputBuffer + * would not be called at all, but after the execution of the first commands + * in the input buffer the client may be blocked, and the "goto again" + * will try to reiterate. The following line will make it return asap. */ + if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return; + + /* Never continue to process the input buffer after QUIT. After the output + * buffer is flushed (with the OK), the connection will be dropped. */ + if (c->flags & REDIS_QUIT) return; + + /* Determine request type when unknown. */ + if (!c->reqtype) { + if (c->querybuf[0] == '*') { + c->reqtype = REDIS_REQ_MULTIBULK; + } else { + c->reqtype = REDIS_REQ_INLINE; + } + } + + if (c->reqtype == REDIS_REQ_INLINE) { + if (processInlineBuffer(c) != REDIS_OK) break; + } else if (c->reqtype == REDIS_REQ_MULTIBULK) { + if (processMultibulkBuffer(c) != REDIS_OK) break; + } else { + redisPanic("Unknown request type"); + } + + /* Multibulk processing could see a <= 0 length. */ + if (c->argc > 0) + processCommand(c); + resetClient(c); + } } void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { @@ -780,14 +848,8 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { return; } if (nread) { - size_t oldlen = sdslen(c->querybuf); - c->querybuf = sdscatlen(c->querybuf, buf, nread); + c->querybuf = sdscatlen(c->querybuf,buf,nread); c->lastinteraction = time(NULL); - /* Scan this new piece of the query for the newline. We do this - * here in order to make sure we perform this scan just one time - * per piece of buffer, leading to an O(N) scan instead of O(N*N) */ - if (c->bulklen == -1 && c->newline == NULL) - c->newline = strchr(c->querybuf+oldlen,'\n'); } else { return; } diff --git a/src/redis.c b/src/redis.c index a1ac2a150..2d61733a4 100644 --- a/src/redis.c +++ b/src/redis.c @@ -889,79 +889,6 @@ void call(redisClient *c, struct redisCommand *cmd) { int processCommand(redisClient *c) { struct redisCommand *cmd; - /* Handle the multi bulk command type. This is an alternative protocol - * supported by Redis in order to receive commands that are composed of - * multiple binary-safe "bulk" arguments. The latency of processing is - * a bit higher but this allows things like multi-sets, so if this - * protocol is used only for MSET and similar commands this is a big win. */ - if (c->multibulk == 0 && c->argc == 1 && ((char*)(c->argv[0]->ptr))[0] == '*') { - c->multibulk = atoi(((char*)c->argv[0]->ptr)+1); - if (c->multibulk <= 0) { - resetClient(c); - return 1; - } else { - decrRefCount(c->argv[c->argc-1]); - c->argc--; - return 1; - } - } else if (c->multibulk) { - if (c->bulklen == -1) { - if (((char*)c->argv[0]->ptr)[0] != '$') { - addReplyError(c,"multi bulk protocol error"); - resetClient(c); - return 1; - } else { - char *eptr; - long bulklen = strtol(((char*)c->argv[0]->ptr)+1,&eptr,10); - int perr = eptr[0] != '\0'; - - decrRefCount(c->argv[0]); - if (perr || bulklen == LONG_MIN || bulklen == LONG_MAX || - bulklen < 0 || bulklen > 1024*1024*1024) - { - c->argc--; - addReplyError(c,"invalid bulk write count"); - resetClient(c); - return 1; - } - c->argc--; - c->bulklen = bulklen+2; /* add two bytes for CR+LF */ - return 1; - } - } else { - c->mbargv = zrealloc(c->mbargv,(sizeof(robj*))*(c->mbargc+1)); - c->mbargv[c->mbargc] = c->argv[0]; - c->mbargc++; - c->argc--; - c->multibulk--; - if (c->multibulk == 0) { - robj **auxargv; - int auxargc; - - /* Here we need to swap the multi-bulk argc/argv with the - * normal argc/argv of the client structure. */ - auxargv = c->argv; - c->argv = c->mbargv; - c->mbargv = auxargv; - - auxargc = c->argc; - c->argc = c->mbargc; - c->mbargc = auxargc; - - /* We need to set bulklen to something different than -1 - * in order for the code below to process the command without - * to try to read the last argument of a bulk command as - * a special argument. */ - c->bulklen = 0; - /* continue below and process the command */ - } else { - c->bulklen = -1; - return 1; - } - } - } - /* -- end of multi bulk commands processing -- */ - /* The QUIT command is handled separately. Normal command procs will * go through checking for replication and QUIT will cause trouble * when FORCE_REPLICATION is enabled and would be implemented in @@ -970,7 +897,7 @@ int processCommand(redisClient *c) { if (!strcasecmp(c->argv[0]->ptr,"quit")) { c->flags |= REDIS_QUIT; addReply(c,shared.ok); - return 0; + return REDIS_ERR; } /* Now lookup the command and check ASAP about trivial error conditions @@ -979,46 +906,14 @@ int processCommand(redisClient *c) { if (!cmd) { addReplyErrorFormat(c,"unknown command '%s'", (char*)c->argv[0]->ptr); - resetClient(c); - return 1; + return REDIS_OK; } else if ((cmd->arity > 0 && cmd->arity != c->argc) || (c->argc < -cmd->arity)) { addReplyErrorFormat(c,"wrong number of arguments for '%s' command", cmd->name); - resetClient(c); - return 1; - } else if (cmd->flags & REDIS_CMD_BULK && c->bulklen == -1) { - /* This is a bulk command, we have to read the last argument yet. */ - char *eptr; - long bulklen = strtol(c->argv[c->argc-1]->ptr,&eptr,10); - int perr = eptr[0] != '\0'; - - decrRefCount(c->argv[c->argc-1]); - if (perr || bulklen == LONG_MAX || bulklen == LONG_MIN || - bulklen < 0 || bulklen > 1024*1024*1024) - { - c->argc--; - addReplyError(c,"invalid bulk write count"); - resetClient(c); - return 1; - } - c->argc--; - c->bulklen = bulklen+2; /* add two bytes for CR+LF */ - /* It is possible that the bulk read is already in the - * buffer. Check this condition and handle it accordingly. - * This is just a fast path, alternative to call processInputBuffer(). - * It's a good idea since the code is small and this condition - * happens most of the times. */ - if ((signed)sdslen(c->querybuf) >= c->bulklen) { - c->argv[c->argc] = createStringObject(c->querybuf,c->bulklen-2); - c->argc++; - c->querybuf = sdsrange(c->querybuf,c->bulklen,-1); - } else { - /* Otherwise return... there is to read the last argument - * from the socket. */ - return 1; - } + return REDIS_OK; } + /* Let's try to encode the bulk object to save space. */ if (cmd->flags & REDIS_CMD_BULK) c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); @@ -1026,8 +921,7 @@ int processCommand(redisClient *c) { /* Check if the user is authenticated */ if (server.requirepass && !c->authenticated && cmd->proc != authCommand) { addReplyError(c,"operation not permitted"); - resetClient(c); - return 1; + return REDIS_OK; } /* Handle the maxmemory directive. @@ -1040,8 +934,7 @@ int processCommand(redisClient *c) { zmalloc_used_memory() > server.maxmemory) { addReplyError(c,"command not allowed when used memory > 'maxmemory'"); - resetClient(c); - return 1; + return REDIS_OK; } /* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */ @@ -1050,8 +943,7 @@ int processCommand(redisClient *c) { cmd->proc != subscribeCommand && cmd->proc != unsubscribeCommand && cmd->proc != psubscribeCommand && cmd->proc != punsubscribeCommand) { addReplyError(c,"only (P)SUBSCRIBE / (P)UNSUBSCRIBE / QUIT allowed in this context"); - resetClient(c); - return 1; + return REDIS_OK; } /* Exec the command */ @@ -1066,10 +958,7 @@ int processCommand(redisClient *c) { blockClientOnSwappedKeys(c,cmd)) return 1; call(c,cmd); } - - /* Prepare the client for the next command */ - resetClient(c); - return 1; + return REDIS_OK; } /*================================== Shutdown =============================== */ diff --git a/src/redis.h b/src/redis.h index e525a99b0..f79b428a9 100644 --- a/src/redis.h +++ b/src/redis.h @@ -145,6 +145,12 @@ #define REDIS_IO_WAIT 32 /* The client is waiting for Virtual Memory I/O */ #define REDIS_DIRTY_CAS 64 /* Watched keys modified. EXEC will fail. */ #define REDIS_QUIT 128 /* Client will be disconnected after reply is sent */ +#define REDIS_CLOSE_AFTER_REPLY 256 /* Close connection immediately once the + * reply has been sent. */ + +/* Client request types */ +#define REDIS_REQ_INLINE 1 +#define REDIS_REQ_MULTIBULK 2 /* Slave replication state - slave side */ #define REDIS_REPL_NONE 0 /* No active replication */ @@ -286,11 +292,11 @@ typedef struct redisClient { redisDb *db; int dictid; sds querybuf; - robj **argv, **mbargv; - char *newline; /* pointing to the detected newline in querybuf */ - int argc, mbargc; - long bulklen; /* bulk read len. -1 if not in bulk read mode */ - int multibulk; /* multi bulk command format active */ + int argc; + robj **argv; + int reqtype; + int multibulklen; /* number of multi bulk arguments left to read */ + long bulklen; /* length of bulk argument in multi bulk request */ list *reply; int sentlen; time_t lastinteraction; /* time of the last interaction, used for timeout */ From 00cf82c0bd9e0272a06f086b00a70d5d5d0f5e31 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 15:50:29 +0200 Subject: [PATCH 05/18] Change tcl client to only use the multibulk protocol --- tests/support/redis.tcl | 44 +++++++---------------------------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/tests/support/redis.tcl b/tests/support/redis.tcl index 98cf86f09..4f8ac485d 100644 --- a/tests/support/redis.tcl +++ b/tests/support/redis.tcl @@ -36,25 +36,6 @@ array set ::redis::deferred {} array set ::redis::callback {} array set ::redis::state {} ;# State in non-blocking reply reading array set ::redis::statestack {} ;# Stack of states, for nested mbulks -array set ::redis::bulkarg {} -array set ::redis::multibulkarg {} - -# Flag commands requiring last argument as a bulk write operation -foreach redis_bulk_cmd { - set setnx rpush lpush rpushx lpushx linsert lset lrem sadd srem sismember echo getset smove zadd zrem zscore zincrby append zrank zrevrank hget hdel hexists setex publish -} { - set ::redis::bulkarg($redis_bulk_cmd) {} -} - -# Flag commands requiring last argument as a bulk write operation -foreach redis_multibulk_cmd { - mset msetnx hset hsetnx hmset hmget -} { - set ::redis::multibulkarg($redis_multibulk_cmd) {} -} - -unset redis_bulk_cmd -unset redis_multibulk_cmd proc redis {{server 127.0.0.1} {port 6379} {defer 0}} { set fd [socket $server $port] @@ -79,25 +60,14 @@ proc ::redis::__dispatch__ {id method args} { set args [lrange $args 0 end-1] } if {[info command ::redis::__method__$method] eq {}} { - if {[info exists ::redis::bulkarg($method)]} { - set cmd "$method " - append cmd [join [lrange $args 0 end-1]] - append cmd " [string length [lindex $args end]]\r\n" - append cmd [lindex $args end] - ::redis::redis_writenl $fd $cmd - } elseif {[info exists ::redis::multibulkarg($method)]} { - set cmd "*[expr {[llength $args]+1}]\r\n" - append cmd "$[string length $method]\r\n$method\r\n" - foreach a $args { - append cmd "$[string length $a]\r\n$a\r\n" - } - ::redis::redis_write $fd $cmd - flush $fd - } else { - set cmd "$method " - append cmd [join $args] - ::redis::redis_writenl $fd $cmd + set cmd "*[expr {[llength $args]+1}]\r\n" + append cmd "$[string length $method]\r\n$method\r\n" + foreach a $args { + append cmd "$[string length $a]\r\n$a\r\n" } + ::redis::redis_write $fd $cmd + flush $fd + if {!$deferred} { if {$blocking} { ::redis::redis_read_reply $fd From 5b12b47df9bfdb92bc6878ee9f9307a2bb413f15 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 15:56:16 +0200 Subject: [PATCH 06/18] Show output of leaks command on a leak --- tests/support/test.tcl | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/support/test.tcl b/tests/support/test.tcl index 93f64928e..e801e1f22 100644 --- a/tests/support/test.tcl +++ b/tests/support/test.tcl @@ -90,8 +90,10 @@ proc test {name code {okpattern notspecified}} { } } if {$::traceleaks} { - if {![string match {*0 leaks*} [exec leaks redis-server]]} { + set output [exec leaks redis-server] + if {![string match {*0 leaks*} $output]} { puts "--------- Test $::testnum LEAKED! --------" + puts $output exit 1 } } From dc11daf3b5d1c2e93f534ee3a47f8a83bcd3796d Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 17:25:20 +0200 Subject: [PATCH 07/18] Change tests to use either the inline or the multibulk protocol --- tests/unit/basic.tcl | 2 +- tests/unit/other.tcl | 2 +- tests/unit/protocol.tcl | 87 ++++++++++++++++++++++------------------- tests/unit/sort.tcl | 18 ++++----- 4 files changed, 58 insertions(+), 51 deletions(-) diff --git a/tests/unit/basic.tcl b/tests/unit/basic.tcl index a8f7feb0b..4c6662c67 100644 --- a/tests/unit/basic.tcl +++ b/tests/unit/basic.tcl @@ -172,7 +172,7 @@ start_server {tags {"basic"}} { test {Commands pipelining} { set fd [r channel] - puts -nonewline $fd "SET k1 4\r\nxyzk\r\nGET k1\r\nPING\r\n" + puts -nonewline $fd "SET k1 xyzk\r\nGET k1\r\nPING\r\n" flush $fd set res {} append res [string match OK* [::redis::redis_read_reply $fd]] diff --git a/tests/unit/other.tcl b/tests/unit/other.tcl index 5967c722d..2e6c0ae17 100644 --- a/tests/unit/other.tcl +++ b/tests/unit/other.tcl @@ -123,7 +123,7 @@ start_server {tags {"other"}} { for {set i 0} {$i < 100000} {incr i} { set q {} set val "0000${i}0000" - append q "SET key:$i [string length $val]\r\n$val\r\n" + append q "SET key:$i $val\r\n" puts -nonewline $fd2 $q set q {} append q "GET key:$i\r\n" diff --git a/tests/unit/protocol.tcl b/tests/unit/protocol.tcl index 5bf42d7fe..d1fadffbc 100644 --- a/tests/unit/protocol.tcl +++ b/tests/unit/protocol.tcl @@ -1,48 +1,55 @@ start_server {tags {"protocol"}} { - test {Handle an empty query well} { - set fd [r channel] - puts -nonewline $fd "\r\n" - flush $fd - r ping - } {PONG} + test "Handle an empty query" { + reconnect + r write "\r\n" + r flush + assert_equal "PONG" [r ping] + } - test {Negative multi bulk command does not create problems} { - set fd [r channel] - puts -nonewline $fd "*-10\r\n" - flush $fd - r ping - } {PONG} + test "Negative multibulk length" { + reconnect + r write "*-10\r\n" + r flush + assert_equal PONG [r ping] + } - test {Negative multi bulk payload} { - set fd [r channel] - puts -nonewline $fd "SET x -10\r\n" - flush $fd - gets $fd - } {*invalid bulk*} + test "Wrong multibulk payload header" { + reconnect + r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\nfooz\r\n" + r flush + assert_error "*expected '$', got 'f'*" {r read} + } - test {Too big bulk payload} { - set fd [r channel] - puts -nonewline $fd "SET x 2000000000\r\n" - flush $fd - gets $fd - } {*invalid bulk*count*} + test "Negative multibulk payload length" { + reconnect + r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$-10\r\n" + r flush + assert_error "*invalid bulk length*" {r read} + } - test {bulk payload is not a number} { - set fd [r channel] - puts -nonewline $fd "SET x blabla\r\n" - flush $fd - gets $fd - } {*invalid bulk*count*} + test "Out of range multibulk payload length" { + reconnect + r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$2000000000\r\n" + r flush + assert_error "*invalid bulk length*" {r read} + } - test {Multi bulk request not followed by bulk args} { - set fd [r channel] - puts -nonewline $fd "*1\r\nfoo\r\n" - flush $fd - gets $fd - } {*protocol error*} + test "Non-number multibulk payload length" { + reconnect + r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$blabla\r\n" + r flush + assert_error "*invalid bulk length*" {r read} + } - test {Generic wrong number of args} { - catch {r ping x y z} err - set _ $err - } {*wrong*arguments*ping*} + test "Multi bulk request not followed by bulk arguments" { + reconnect + r write "*1\r\nfoo\r\n" + r flush + assert_error "*expected '$', got 'f'*" {r read} + } + + test "Generic wrong number of args" { + reconnect + assert_error "*wrong*arguments*ping*" {r ping x y z} + } } diff --git a/tests/unit/sort.tcl b/tests/unit/sort.tcl index dcc471fb5..415585223 100644 --- a/tests/unit/sort.tcl +++ b/tests/unit/sort.tcl @@ -47,11 +47,11 @@ start_server { assert_encoding $enc tosort test "$title: SORT BY key" { - assert_equal $result [r sort tosort {BY weight_*}] + assert_equal $result [r sort tosort BY weight_*] } test "$title: SORT BY hash field" { - assert_equal $result [r sort tosort {BY wobj_*->weight}] + assert_equal $result [r sort tosort BY wobj_*->weight] } } @@ -78,21 +78,21 @@ start_server { } test "SORT BY key STORE" { - r sort tosort {BY weight_*} store sort-res + r sort tosort BY weight_* store sort-res assert_equal $result [r lrange sort-res 0 -1] assert_equal 16 [r llen sort-res] assert_encoding ziplist sort-res } test "SORT BY hash field STORE" { - r sort tosort {BY wobj_*->weight} store sort-res + r sort tosort BY wobj_*->weight store sort-res assert_equal $result [r lrange sort-res 0 -1] assert_equal 16 [r llen sort-res] assert_encoding ziplist sort-res } test "SORT DESC" { - assert_equal [lsort -decreasing -integer $result] [r sort tosort {DESC}] + assert_equal [lsort -decreasing -integer $result] [r sort tosort DESC] } test "SORT ALPHA against integer encoded strings" { @@ -141,7 +141,7 @@ start_server { test "SORT speed, $num element list BY key, 100 times" { set start [clock clicks -milliseconds] for {set i 0} {$i < 100} {incr i} { - set sorted [r sort tosort {BY weight_* LIMIT 0 10}] + set sorted [r sort tosort BY weight_* LIMIT 0 10] } set elapsed [expr [clock clicks -milliseconds]-$start] puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds " @@ -151,7 +151,7 @@ start_server { test "SORT speed, $num element list BY hash field, 100 times" { set start [clock clicks -milliseconds] for {set i 0} {$i < 100} {incr i} { - set sorted [r sort tosort {BY wobj_*->weight LIMIT 0 10}] + set sorted [r sort tosort BY wobj_*->weight LIMIT 0 10] } set elapsed [expr [clock clicks -milliseconds]-$start] puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds " @@ -161,7 +161,7 @@ start_server { test "SORT speed, $num element list directly, 100 times" { set start [clock clicks -milliseconds] for {set i 0} {$i < 100} {incr i} { - set sorted [r sort tosort {LIMIT 0 10}] + set sorted [r sort tosort LIMIT 0 10] } set elapsed [expr [clock clicks -milliseconds]-$start] puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds " @@ -171,7 +171,7 @@ start_server { test "SORT speed, $num element list BY , 100 times" { set start [clock clicks -milliseconds] for {set i 0} {$i < 100} {incr i} { - set sorted [r sort tosort {BY nokey LIMIT 0 10}] + set sorted [r sort tosort BY nokey LIMIT 0 10] } set elapsed [expr [clock clicks -milliseconds]-$start] puts -nonewline "\n Average time to sort: [expr double($elapsed)/100] milliseconds " From 9da6caac4e86aa608a3186fa8a209eed19b7b9c6 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 17:27:05 +0200 Subject: [PATCH 08/18] Don't reset the client when processCommand returns REDIS_ERR --- src/networking.c | 10 +++++++--- src/redis.c | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/networking.c b/src/networking.c index cc4c9341d..e2e25207b 100644 --- a/src/networking.c +++ b/src/networking.c @@ -820,9 +820,13 @@ void processInputBuffer(redisClient *c) { } /* Multibulk processing could see a <= 0 length. */ - if (c->argc > 0) - processCommand(c); - resetClient(c); + if (c->argc == 0) { + resetClient(c); + } else { + /* Only reset the client when the command was executed. */ + if (processCommand(c) == REDIS_OK) + resetClient(c); + } } } diff --git a/src/redis.c b/src/redis.c index 2d61733a4..f4e244f03 100644 --- a/src/redis.c +++ b/src/redis.c @@ -955,7 +955,7 @@ int processCommand(redisClient *c) { addReply(c,shared.queued); } else { if (server.vm_enabled && server.vm_max_threads > 0 && - blockClientOnSwappedKeys(c,cmd)) return 1; + blockClientOnSwappedKeys(c,cmd)) return REDIS_ERR; call(c,cmd); } return REDIS_OK; From 1aa608fc68877bcf7c7f287cf7a373fd6ef8a92c Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 17:34:20 +0200 Subject: [PATCH 09/18] Change protocol from bulk to inline in redis-benchmark --- src/redis-benchmark.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c index 297ecc6c5..ce2301806 100644 --- a/src/redis-benchmark.c +++ b/src/redis-benchmark.c @@ -578,7 +578,7 @@ int main(int argc, char **argv) { prepareForBenchmark("SET"); c = createClient(); if (!c) exit(1); - c->obuf = sdscatprintf(c->obuf,"SET foo_rand000000000000 %d\r\n",config.datasize); + c->obuf = sdscat(c->obuf,"SET foo_rand000000000000 "); { char *data = zmalloc(config.datasize+2); memset(data,'x',config.datasize); @@ -612,7 +612,7 @@ int main(int argc, char **argv) { prepareForBenchmark("LPUSH"); c = createClient(); if (!c) exit(1); - c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n"); + c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n"); prepareClientForReply(c,REPLY_INT); createMissingClients(c); aeMain(config.el); @@ -630,7 +630,7 @@ int main(int argc, char **argv) { prepareForBenchmark("SADD"); c = createClient(); if (!c) exit(1); - c->obuf = sdscat(c->obuf,"SADD myset 24\r\ncounter_rand000000000000\r\n"); + c->obuf = sdscat(c->obuf,"SADD myset counter_rand000000000000\r\n"); prepareClientForReply(c,REPLY_RETCODE); createMissingClients(c); aeMain(config.el); @@ -648,7 +648,7 @@ int main(int argc, char **argv) { prepareForBenchmark("LPUSH (again, in order to bench LRANGE)"); c = createClient(); if (!c) exit(1); - c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n"); + c->obuf = sdscat(c->obuf,"LPUSH mylist bar\r\n"); prepareClientForReply(c,REPLY_RETCODE); createMissingClients(c); aeMain(config.el); From ea5b70924d444c5ebf32afdd577ddc7c1328a674 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 18:17:06 +0200 Subject: [PATCH 10/18] Add benchmark for MSET --- src/redis-benchmark.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/redis-benchmark.c b/src/redis-benchmark.c index ce2301806..c5ababf2a 100644 --- a/src/redis-benchmark.c +++ b/src/redis-benchmark.c @@ -575,6 +575,24 @@ int main(int argc, char **argv) { aeMain(config.el); endBenchmark(); + prepareForBenchmark("MSET (10 keys, multi bulk)"); + c = createClient(); + if (!c) exit(1); + c->obuf = sdscatprintf(c->obuf,"*%d\r\n$4\r\nMSET\r\n", 11); + { + int i; + char *data = zmalloc(config.datasize+2); + memset(data,'x',config.datasize); + for (i = 0; i < 10; i++) { + c->obuf = sdscatprintf(c->obuf,"$%d\r\n%s\r\n",config.datasize,data); + } + zfree(data); + } + prepareClientForReply(c,REPLY_RETCODE); + createMissingClients(c); + aeMain(config.el); + endBenchmark(); + prepareForBenchmark("SET"); c = createClient(); if (!c) exit(1); From b19c33d48a3b5456b87f84c85fdd85592e0c0562 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Fri, 15 Oct 2010 19:15:38 +0200 Subject: [PATCH 11/18] Prevent clients from making too large multibulk requests --- src/networking.c | 4 ++++ tests/unit/protocol.tcl | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/src/networking.c b/src/networking.c index e2e25207b..46d49bf6c 100644 --- a/src/networking.c +++ b/src/networking.c @@ -724,6 +724,10 @@ int processMultibulkBuffer(redisClient *c) { if (c->multibulklen <= 0) { c->querybuf = sdsrange(c->querybuf,pos,-1); return REDIS_OK; + } else if (c->multibulklen > 1024*1024) { + addReplyError(c,"Protocol error: invalid multibulk length"); + setProtocolError(c,pos); + return REDIS_ERR; } /* Setup argv array on client structure */ diff --git a/tests/unit/protocol.tcl b/tests/unit/protocol.tcl index d1fadffbc..b0faf5dd7 100644 --- a/tests/unit/protocol.tcl +++ b/tests/unit/protocol.tcl @@ -13,6 +13,13 @@ start_server {tags {"protocol"}} { assert_equal PONG [r ping] } + test "Out of range multibulk length" { + reconnect + r write "*20000000\r\n" + r flush + assert_error "*invalid multibulk length*" {r read} + } + test "Wrong multibulk payload header" { reconnect r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\nfooz\r\n" From 75b41de8ca975d01c6371a98f9e552c934af5937 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Sun, 17 Oct 2010 17:21:41 +0200 Subject: [PATCH 12/18] Convert objects in the command procs instead of the protocol code --- src/aof.c | 3 --- src/config.c | 1 + src/pubsub.c | 1 + src/redis.c | 5 +---- src/t_hash.c | 7 +++++++ src/t_list.c | 9 +++++++-- src/t_set.c | 5 ++++- src/t_string.c | 5 +++++ src/t_zset.c | 5 +++++ 9 files changed, 31 insertions(+), 10 deletions(-) diff --git a/src/aof.c b/src/aof.c index 167134818..36d97e707 100644 --- a/src/aof.c +++ b/src/aof.c @@ -266,9 +266,6 @@ int loadAppendOnlyFile(char *filename) { redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr); exit(1); } - /* Try object encoding */ - if (cmd->flags & REDIS_CMD_BULK) - argv[argc-1] = tryObjectEncoding(argv[argc-1]); /* Run the command in the context of a fake client */ fakeClient->argc = argc; fakeClient->argv = argv; diff --git a/src/config.c b/src/config.c index 1bd678c78..ff4c130f4 100644 --- a/src/config.c +++ b/src/config.c @@ -410,6 +410,7 @@ void configGetCommand(redisClient *c) { } void configCommand(redisClient *c) { + c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); if (!strcasecmp(c->argv[1]->ptr,"set")) { if (c->argc != 4) goto badarity; configSetCommand(c); diff --git a/src/pubsub.c b/src/pubsub.c index 2bd3d0580..23b0ceb0b 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -262,6 +262,7 @@ void punsubscribeCommand(redisClient *c) { } void publishCommand(redisClient *c) { + c->argv[2] = tryObjectEncoding(c->argv[2]); int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]); addReplyLongLong(c,receivers); } diff --git a/src/redis.c b/src/redis.c index f4e244f03..f770edc70 100644 --- a/src/redis.c +++ b/src/redis.c @@ -914,10 +914,6 @@ int processCommand(redisClient *c) { return REDIS_OK; } - /* Let's try to encode the bulk object to save space. */ - if (cmd->flags & REDIS_CMD_BULK) - c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); - /* Check if the user is authenticated */ if (server.requirepass && !c->authenticated && cmd->proc != authCommand) { addReplyError(c,"operation not permitted"); @@ -1011,6 +1007,7 @@ void pingCommand(redisClient *c) { } void echoCommand(redisClient *c) { + c->argv[1] = tryObjectEncoding(c->argv[1]); addReplyBulk(c,c->argv[1]); } diff --git a/src/t_hash.c b/src/t_hash.c index 5cef1cabb..0f568b97c 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -220,6 +220,7 @@ void hsetCommand(redisClient *c) { robj *o; if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + c->argv[3] = tryObjectEncoding(c->argv[3]); hashTypeTryConversion(o,c->argv,2,3); hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); update = hashTypeSet(o,c->argv[2],c->argv[3]); @@ -231,6 +232,7 @@ void hsetCommand(redisClient *c) { void hsetnxCommand(redisClient *c) { robj *o; if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + c->argv[3] = tryObjectEncoding(c->argv[3]); hashTypeTryConversion(o,c->argv,2,3); if (hashTypeExists(o, c->argv[2])) { @@ -254,6 +256,7 @@ void hmsetCommand(redisClient *c) { } if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; + c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); hashTypeTryConversion(o,c->argv,2,c->argc-1); for (i = 2; i < c->argc; i += 2) { hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); @@ -296,6 +299,7 @@ void hgetCommand(redisClient *c) { if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,o,REDIS_HASH)) return; + c->argv[2] = tryObjectEncoding(c->argv[2]); if ((value = hashTypeGet(o,c->argv[2])) != NULL) { addReplyBulk(c,value); decrRefCount(value); @@ -316,6 +320,7 @@ void hmgetCommand(redisClient *c) { * done because objects that cannot be found are considered to be * an empty hash. The reply should then be a series of NULLs. */ addReplyMultiBulkLen(c,c->argc-2); + c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); for (i = 2; i < c->argc; i++) { if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) { addReplyBulk(c,value); @@ -331,6 +336,7 @@ void hdelCommand(redisClient *c) { if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_HASH)) return; + c->argv[2] = tryObjectEncoding(c->argv[2]); if (hashTypeDelete(o,c->argv[2])) { if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); addReply(c,shared.cone); @@ -395,5 +401,6 @@ void hexistsCommand(redisClient *c) { if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_HASH)) return; + c->argv[2] = tryObjectEncoding(c->argv[2]); addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); } diff --git a/src/t_list.c b/src/t_list.c index 41d651f64..42e1d5873 100644 --- a/src/t_list.c +++ b/src/t_list.c @@ -260,6 +260,7 @@ void listTypeConvert(robj *subject, int enc) { void pushGenericCommand(redisClient *c, int where) { robj *lobj = lookupKeyWrite(c->db,c->argv[1]); + c->argv[2] = tryObjectEncoding(c->argv[2]); if (lobj == NULL) { if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { addReply(c,shared.cone); @@ -346,14 +347,17 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { } void lpushxCommand(redisClient *c) { + c->argv[2] = tryObjectEncoding(c->argv[2]); pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD); } void rpushxCommand(redisClient *c) { + c->argv[2] = tryObjectEncoding(c->argv[2]); pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL); } void linsertCommand(redisClient *c) { + c->argv[4] = tryObjectEncoding(c->argv[4]); if (strcasecmp(c->argv[2]->ptr,"after") == 0) { pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL); } else if (strcasecmp(c->argv[2]->ptr,"before") == 0) { @@ -409,7 +413,7 @@ void lsetCommand(redisClient *c) { robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr); if (o == NULL || checkType(c,o,REDIS_LIST)) return; int index = atoi(c->argv[2]->ptr); - robj *value = c->argv[3]; + robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3])); listTypeTryConversion(o,value); if (o->encoding == REDIS_ENCODING_ZIPLIST) { @@ -559,7 +563,8 @@ void ltrimCommand(redisClient *c) { } void lremCommand(redisClient *c) { - robj *subject, *obj = c->argv[3]; + robj *subject, *obj; + obj = c->argv[3] = tryObjectEncoding(c->argv[3]); int toremove = atoi(c->argv[2]->ptr); int removed = 0; listTypeEntry entry; diff --git a/src/t_set.c b/src/t_set.c index e2ac5ae53..234efc7de 100644 --- a/src/t_set.c +++ b/src/t_set.c @@ -178,6 +178,7 @@ void saddCommand(redisClient *c) { robj *set; set = lookupKeyWrite(c->db,c->argv[1]); + c->argv[2] = tryObjectEncoding(c->argv[2]); if (set == NULL) { set = setTypeCreate(c->argv[2]); dbAdd(c->db,c->argv[1],set); @@ -202,6 +203,7 @@ void sremCommand(redisClient *c) { if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,set,REDIS_SET)) return; + c->argv[2] = tryObjectEncoding(c->argv[2]); if (setTypeRemove(set,c->argv[2])) { if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]); touchWatchedKey(c->db,c->argv[1]); @@ -216,7 +218,7 @@ void smoveCommand(redisClient *c) { robj *srcset, *dstset, *ele; srcset = lookupKeyWrite(c->db,c->argv[1]); dstset = lookupKeyWrite(c->db,c->argv[2]); - ele = c->argv[3]; + ele = c->argv[3] = tryObjectEncoding(c->argv[3]); /* If the source key does not exist return 0 */ if (srcset == NULL) { @@ -264,6 +266,7 @@ void sismemberCommand(redisClient *c) { if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,set,REDIS_SET)) return; + c->argv[2] = tryObjectEncoding(c->argv[2]); if (setTypeIsMember(set,c->argv[2])) addReply(c,shared.cone); else diff --git a/src/t_string.c b/src/t_string.c index 509c630a4..39ee506d5 100644 --- a/src/t_string.c +++ b/src/t_string.c @@ -37,14 +37,17 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir } void setCommand(redisClient *c) { + c->argv[2] = tryObjectEncoding(c->argv[2]); setGenericCommand(c,0,c->argv[1],c->argv[2],NULL); } void setnxCommand(redisClient *c) { + c->argv[2] = tryObjectEncoding(c->argv[2]); setGenericCommand(c,1,c->argv[1],c->argv[2],NULL); } void setexCommand(redisClient *c) { + c->argv[3] = tryObjectEncoding(c->argv[3]); setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]); } @@ -69,6 +72,7 @@ void getCommand(redisClient *c) { void getsetCommand(redisClient *c) { if (getGenericCommand(c) == REDIS_ERR) return; + c->argv[2] = tryObjectEncoding(c->argv[2]); dbReplace(c->db,c->argv[1],c->argv[2]); incrRefCount(c->argv[2]); touchWatchedKey(c->db,c->argv[1]); @@ -180,6 +184,7 @@ void appendCommand(redisClient *c) { robj *o; o = lookupKeyWrite(c->db,c->argv[1]); + c->argv[2] = tryObjectEncoding(c->argv[2]); if (o == NULL) { /* Create the key */ retval = dbAdd(c->db,c->argv[1],c->argv[2]); diff --git a/src/t_zset.c b/src/t_zset.c index 114c95d62..ba05c2789 100644 --- a/src/t_zset.c +++ b/src/t_zset.c @@ -392,12 +392,14 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int void zaddCommand(redisClient *c) { double scoreval; if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return; + c->argv[3] = tryObjectEncoding(c->argv[3]); zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0); } void zincrbyCommand(redisClient *c) { double scoreval; if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return; + c->argv[3] = tryObjectEncoding(c->argv[3]); zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1); } @@ -412,6 +414,7 @@ void zremCommand(redisClient *c) { checkType(c,zsetobj,REDIS_ZSET)) return; zs = zsetobj->ptr; + c->argv[2] = tryObjectEncoding(c->argv[2]); de = dictFind(zs->dict,c->argv[2]); if (de == NULL) { addReply(c,shared.czero); @@ -921,6 +924,7 @@ void zscoreCommand(redisClient *c) { checkType(c,o,REDIS_ZSET)) return; zs = o->ptr; + c->argv[2] = tryObjectEncoding(c->argv[2]); de = dictFind(zs->dict,c->argv[2]); if (!de) { addReply(c,shared.nullbulk); @@ -944,6 +948,7 @@ void zrankGenericCommand(redisClient *c, int reverse) { zs = o->ptr; zsl = zs->zsl; + c->argv[2] = tryObjectEncoding(c->argv[2]); de = dictFind(zs->dict,c->argv[2]); if (!de) { addReply(c,shared.nullbulk); From 33aba595b0ad3baae1c110bfd4090dfe337b486f Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Sun, 17 Oct 2010 17:31:40 +0200 Subject: [PATCH 13/18] Removed unused command flags --- src/redis-cli.c | 4 - src/redis.c | 226 ++++++++++++++++++++++++------------------------ src/redis.h | 18 ++-- 3 files changed, 122 insertions(+), 126 deletions(-) diff --git a/src/redis-cli.c b/src/redis-cli.c index 5071604b8..aa7306b44 100644 --- a/src/redis-cli.c +++ b/src/redis-cli.c @@ -45,10 +45,6 @@ #include "zmalloc.h" #include "linenoise.h" -#define REDIS_CMD_INLINE 1 -#define REDIS_CMD_BULK 2 -#define REDIS_CMD_MULTIBULK 4 - #define REDIS_NOTUSED(V) ((void) V) static struct config { diff --git a/src/redis.c b/src/redis.c index f770edc70..a3f04f112 100644 --- a/src/redis.c +++ b/src/redis.c @@ -69,119 +69,119 @@ double R_Zero, R_PosInf, R_NegInf, R_Nan; struct redisServer server; /* server global state */ struct redisCommand *commandTable; struct redisCommand readonlyCommandTable[] = { - {"get",getCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"set",setCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, - {"setnx",setnxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, - {"setex",setexCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,0,0,0}, - {"append",appendCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"substr",substrCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"strlen",strlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"del",delCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"exists",existsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"incr",incrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"decr",decrCommand,2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"mget",mgetCommand,-2,REDIS_CMD_INLINE,NULL,1,-1,1}, - {"rpush",rpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"lpush",lpushCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"rpushx",rpushxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"lpushx",lpushxCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"linsert",linsertCommand,5,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"rpop",rpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"lpop",lpopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"brpop",brpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"blpop",blpopCommand,-3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"llen",llenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"lindex",lindexCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"lset",lsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"lrange",lrangeCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"ltrim",ltrimCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"lrem",lremCommand,4,REDIS_CMD_BULK,NULL,1,1,1}, - {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,2,1}, - {"sadd",saddCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"srem",sremCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"smove",smoveCommand,4,REDIS_CMD_BULK,NULL,1,2,1}, - {"sismember",sismemberCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"scard",scardCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"spop",spopCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"srandmember",srandmemberCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"sinter",sinterCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, - {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, - {"sunion",sunionCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, - {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, - {"sdiff",sdiffCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,-1,1}, - {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,2,-1,1}, - {"smembers",sinterCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zadd",zaddCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"zincrby",zincrbyCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"zrem",zremCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"zremrangebyscore",zremrangebyscoreCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zremrangebyrank",zremrangebyrankCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zunionstore",zunionstoreCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, - {"zinterstore",zinterstoreCommand,-4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, - {"zrange",zrangeCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zrangebyscore",zrangebyscoreCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zcount",zcountCommand,4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zrevrange",zrevrangeCommand,-4,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zcard",zcardCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"zscore",zscoreCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"zrank",zrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"zrevrank",zrevrankCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"hset",hsetCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"hsetnx",hsetnxCommand,4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"hget",hgetCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"hmset",hmsetCommand,-4,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"hmget",hmgetCommand,-3,REDIS_CMD_BULK,NULL,1,1,1}, - {"hincrby",hincrbyCommand,4,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"hdel",hdelCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"hlen",hlenCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"hkeys",hkeysCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"hvals",hvalsCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"hgetall",hgetallCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"hexists",hexistsCommand,3,REDIS_CMD_BULK,NULL,1,1,1}, - {"incrby",incrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"decrby",decrbyCommand,3,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"getset",getsetCommand,3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"mset",msetCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,-1,2}, - {"msetnx",msetnxCommand,-3,REDIS_CMD_BULK|REDIS_CMD_DENYOOM,NULL,1,-1,2}, - {"randomkey",randomkeyCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"select",selectCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"move",moveCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"rename",renameCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"renamenx",renamenxCommand,3,REDIS_CMD_INLINE,NULL,1,1,1}, - {"expire",expireCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, - {"expireat",expireatCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, - {"keys",keysCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"dbsize",dbsizeCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"auth",authCommand,2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"ping",pingCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"echo",echoCommand,2,REDIS_CMD_BULK,NULL,0,0,0}, - {"save",saveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"bgsave",bgsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"bgrewriteaof",bgrewriteaofCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"shutdown",shutdownCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"lastsave",lastsaveCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"type",typeCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"multi",multiCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"exec",execCommand,1,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,execBlockClientOnSwappedKeys,0,0,0}, - {"discard",discardCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"sync",syncCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"flushdb",flushdbCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"flushall",flushallCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"sort",sortCommand,-2,REDIS_CMD_INLINE|REDIS_CMD_DENYOOM,NULL,1,1,1}, - {"info",infoCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"monitor",monitorCommand,1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"ttl",ttlCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"persist",persistCommand,2,REDIS_CMD_INLINE,NULL,1,1,1}, - {"slaveof",slaveofCommand,3,REDIS_CMD_INLINE,NULL,0,0,0}, - {"debug",debugCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"config",configCommand,-2,REDIS_CMD_BULK,NULL,0,0,0}, - {"subscribe",subscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"unsubscribe",unsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"psubscribe",psubscribeCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"punsubscribe",punsubscribeCommand,-1,REDIS_CMD_INLINE,NULL,0,0,0}, - {"publish",publishCommand,3,REDIS_CMD_BULK|REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0}, - {"watch",watchCommand,-2,REDIS_CMD_INLINE,NULL,0,0,0}, - {"unwatch",unwatchCommand,1,REDIS_CMD_INLINE,NULL,0,0,0} + {"get",getCommand,2,0,NULL,1,1,1}, + {"set",setCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"setnx",setnxCommand,3,REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"setex",setexCommand,4,REDIS_CMD_DENYOOM,NULL,0,0,0}, + {"append",appendCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"substr",substrCommand,4,0,NULL,1,1,1}, + {"strlen",strlenCommand,2,0,NULL,1,1,1}, + {"del",delCommand,-2,0,NULL,0,0,0}, + {"exists",existsCommand,2,0,NULL,1,1,1}, + {"incr",incrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"decr",decrCommand,2,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"mget",mgetCommand,-2,0,NULL,1,-1,1}, + {"rpush",rpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"lpush",lpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"rpushx",rpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"lpushx",lpushxCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"linsert",linsertCommand,5,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"rpop",rpopCommand,2,0,NULL,1,1,1}, + {"lpop",lpopCommand,2,0,NULL,1,1,1}, + {"brpop",brpopCommand,-3,0,NULL,1,1,1}, + {"blpop",blpopCommand,-3,0,NULL,1,1,1}, + {"llen",llenCommand,2,0,NULL,1,1,1}, + {"lindex",lindexCommand,3,0,NULL,1,1,1}, + {"lset",lsetCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"lrange",lrangeCommand,4,0,NULL,1,1,1}, + {"ltrim",ltrimCommand,4,0,NULL,1,1,1}, + {"lrem",lremCommand,4,0,NULL,1,1,1}, + {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1}, + {"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"srem",sremCommand,3,0,NULL,1,1,1}, + {"smove",smoveCommand,4,0,NULL,1,2,1}, + {"sismember",sismemberCommand,3,0,NULL,1,1,1}, + {"scard",scardCommand,2,0,NULL,1,1,1}, + {"spop",spopCommand,2,0,NULL,1,1,1}, + {"srandmember",srandmemberCommand,2,0,NULL,1,1,1}, + {"sinter",sinterCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sinterstore",sinterstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"sunion",sunionCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sunionstore",sunionstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"sdiff",sdiffCommand,-2,REDIS_CMD_DENYOOM,NULL,1,-1,1}, + {"sdiffstore",sdiffstoreCommand,-3,REDIS_CMD_DENYOOM,NULL,2,-1,1}, + {"smembers",sinterCommand,2,0,NULL,1,1,1}, + {"zadd",zaddCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zincrby",zincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zrem",zremCommand,3,0,NULL,1,1,1}, + {"zremrangebyscore",zremrangebyscoreCommand,4,0,NULL,1,1,1}, + {"zremrangebyrank",zremrangebyrankCommand,4,0,NULL,1,1,1}, + {"zunionstore",zunionstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, + {"zinterstore",zinterstoreCommand,-4,REDIS_CMD_DENYOOM,zunionInterBlockClientOnSwappedKeys,0,0,0}, + {"zrange",zrangeCommand,-4,0,NULL,1,1,1}, + {"zrangebyscore",zrangebyscoreCommand,-4,0,NULL,1,1,1}, + {"zcount",zcountCommand,4,0,NULL,1,1,1}, + {"zrevrange",zrevrangeCommand,-4,0,NULL,1,1,1}, + {"zcard",zcardCommand,2,0,NULL,1,1,1}, + {"zscore",zscoreCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"zrank",zrankCommand,3,0,NULL,1,1,1}, + {"zrevrank",zrevrankCommand,3,0,NULL,1,1,1}, + {"hset",hsetCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hsetnx",hsetnxCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hget",hgetCommand,3,0,NULL,1,1,1}, + {"hmset",hmsetCommand,-4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hmget",hmgetCommand,-3,0,NULL,1,1,1}, + {"hincrby",hincrbyCommand,4,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"hdel",hdelCommand,3,0,NULL,1,1,1}, + {"hlen",hlenCommand,2,0,NULL,1,1,1}, + {"hkeys",hkeysCommand,2,0,NULL,1,1,1}, + {"hvals",hvalsCommand,2,0,NULL,1,1,1}, + {"hgetall",hgetallCommand,2,0,NULL,1,1,1}, + {"hexists",hexistsCommand,3,0,NULL,1,1,1}, + {"incrby",incrbyCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"decrby",decrbyCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"getset",getsetCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"mset",msetCommand,-3,REDIS_CMD_DENYOOM,NULL,1,-1,2}, + {"msetnx",msetnxCommand,-3,REDIS_CMD_DENYOOM,NULL,1,-1,2}, + {"randomkey",randomkeyCommand,1,0,NULL,0,0,0}, + {"select",selectCommand,2,0,NULL,0,0,0}, + {"move",moveCommand,3,0,NULL,1,1,1}, + {"rename",renameCommand,3,0,NULL,1,1,1}, + {"renamenx",renamenxCommand,3,0,NULL,1,1,1}, + {"expire",expireCommand,3,0,NULL,0,0,0}, + {"expireat",expireatCommand,3,0,NULL,0,0,0}, + {"keys",keysCommand,2,0,NULL,0,0,0}, + {"dbsize",dbsizeCommand,1,0,NULL,0,0,0}, + {"auth",authCommand,2,0,NULL,0,0,0}, + {"ping",pingCommand,1,0,NULL,0,0,0}, + {"echo",echoCommand,2,0,NULL,0,0,0}, + {"save",saveCommand,1,0,NULL,0,0,0}, + {"bgsave",bgsaveCommand,1,0,NULL,0,0,0}, + {"bgrewriteaof",bgrewriteaofCommand,1,0,NULL,0,0,0}, + {"shutdown",shutdownCommand,1,0,NULL,0,0,0}, + {"lastsave",lastsaveCommand,1,0,NULL,0,0,0}, + {"type",typeCommand,2,0,NULL,1,1,1}, + {"multi",multiCommand,1,0,NULL,0,0,0}, + {"exec",execCommand,1,REDIS_CMD_DENYOOM,execBlockClientOnSwappedKeys,0,0,0}, + {"discard",discardCommand,1,0,NULL,0,0,0}, + {"sync",syncCommand,1,0,NULL,0,0,0}, + {"flushdb",flushdbCommand,1,0,NULL,0,0,0}, + {"flushall",flushallCommand,1,0,NULL,0,0,0}, + {"sort",sortCommand,-2,REDIS_CMD_DENYOOM,NULL,1,1,1}, + {"info",infoCommand,1,0,NULL,0,0,0}, + {"monitor",monitorCommand,1,0,NULL,0,0,0}, + {"ttl",ttlCommand,2,0,NULL,1,1,1}, + {"persist",persistCommand,2,0,NULL,1,1,1}, + {"slaveof",slaveofCommand,3,0,NULL,0,0,0}, + {"debug",debugCommand,-2,0,NULL,0,0,0}, + {"config",configCommand,-2,0,NULL,0,0,0}, + {"subscribe",subscribeCommand,-2,0,NULL,0,0,0}, + {"unsubscribe",unsubscribeCommand,-1,0,NULL,0,0,0}, + {"psubscribe",psubscribeCommand,-2,0,NULL,0,0,0}, + {"punsubscribe",punsubscribeCommand,-1,0,NULL,0,0,0}, + {"publish",publishCommand,3,REDIS_CMD_FORCE_REPLICATION,NULL,0,0,0}, + {"watch",watchCommand,-2,0,NULL,0,0,0}, + {"unwatch",unwatchCommand,1,0,NULL,0,0,0} }; /*============================ Utility functions ============================ */ diff --git a/src/redis.h b/src/redis.h index f79b428a9..1e841b73f 100644 --- a/src/redis.h +++ b/src/redis.h @@ -57,15 +57,15 @@ /* Hash table parameters */ #define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */ -/* Command flags */ -#define REDIS_CMD_BULK 1 /* Bulk write command */ -#define REDIS_CMD_INLINE 2 /* Inline command */ -/* REDIS_CMD_DENYOOM reserves a longer comment: all the commands marked with - this flags will return an error when the 'maxmemory' option is set in the - config file and the server is using more than maxmemory bytes of memory. - In short this commands are denied on low memory conditions. */ -#define REDIS_CMD_DENYOOM 4 -#define REDIS_CMD_FORCE_REPLICATION 8 /* Force replication even if dirty is 0 */ +/* Command flags: + * REDIS_CMD_DENYOOM: + * Commands marked with this flag will return an error when 'maxmemory' is + * set and the server is using more than 'maxmemory' bytes of memory. + * In short: commands with this flag are denied on low memory conditions. + * REDIS_CMD_FORCE_REPLICATION: + * Force replication even if dirty is 0. */ +#define REDIS_CMD_DENYOOM 4 +#define REDIS_CMD_FORCE_REPLICATION 8 /* Object types */ #define REDIS_STRING 0 From 19408d83a41bd3a1161bd6e5240eb1c60091d40b Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Sun, 17 Oct 2010 17:46:55 +0200 Subject: [PATCH 14/18] Object encoding in hash function is done by a more specific function --- src/t_hash.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/t_hash.c b/src/t_hash.c index 0f568b97c..5cef1cabb 100644 --- a/src/t_hash.c +++ b/src/t_hash.c @@ -220,7 +220,6 @@ void hsetCommand(redisClient *c) { robj *o; if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; - c->argv[3] = tryObjectEncoding(c->argv[3]); hashTypeTryConversion(o,c->argv,2,3); hashTypeTryObjectEncoding(o,&c->argv[2], &c->argv[3]); update = hashTypeSet(o,c->argv[2],c->argv[3]); @@ -232,7 +231,6 @@ void hsetCommand(redisClient *c) { void hsetnxCommand(redisClient *c) { robj *o; if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; - c->argv[3] = tryObjectEncoding(c->argv[3]); hashTypeTryConversion(o,c->argv,2,3); if (hashTypeExists(o, c->argv[2])) { @@ -256,7 +254,6 @@ void hmsetCommand(redisClient *c) { } if ((o = hashTypeLookupWriteOrCreate(c,c->argv[1])) == NULL) return; - c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); hashTypeTryConversion(o,c->argv,2,c->argc-1); for (i = 2; i < c->argc; i += 2) { hashTypeTryObjectEncoding(o,&c->argv[i], &c->argv[i+1]); @@ -299,7 +296,6 @@ void hgetCommand(redisClient *c) { if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk)) == NULL || checkType(c,o,REDIS_HASH)) return; - c->argv[2] = tryObjectEncoding(c->argv[2]); if ((value = hashTypeGet(o,c->argv[2])) != NULL) { addReplyBulk(c,value); decrRefCount(value); @@ -320,7 +316,6 @@ void hmgetCommand(redisClient *c) { * done because objects that cannot be found are considered to be * an empty hash. The reply should then be a series of NULLs. */ addReplyMultiBulkLen(c,c->argc-2); - c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); for (i = 2; i < c->argc; i++) { if (o != NULL && (value = hashTypeGet(o,c->argv[i])) != NULL) { addReplyBulk(c,value); @@ -336,7 +331,6 @@ void hdelCommand(redisClient *c) { if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_HASH)) return; - c->argv[2] = tryObjectEncoding(c->argv[2]); if (hashTypeDelete(o,c->argv[2])) { if (hashTypeLength(o) == 0) dbDelete(c->db,c->argv[1]); addReply(c,shared.cone); @@ -401,6 +395,5 @@ void hexistsCommand(redisClient *c) { if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || checkType(c,o,REDIS_HASH)) return; - c->argv[2] = tryObjectEncoding(c->argv[2]); addReply(c, hashTypeExists(o,c->argv[2]) ? shared.cone : shared.czero); } From a375b077cc1da6afee6497749e4e3512caa757c7 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Sun, 17 Oct 2010 18:09:23 +0200 Subject: [PATCH 15/18] Skip object encoding where it doesn't make sense --- src/config.c | 14 ++++++-------- src/pubsub.c | 1 - src/redis.c | 1 - 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/config.c b/src/config.c index ff4c130f4..c979162bc 100644 --- a/src/config.c +++ b/src/config.c @@ -225,8 +225,11 @@ loaderr: *----------------------------------------------------------------------------*/ void configSetCommand(redisClient *c) { - robj *o = getDecodedObject(c->argv[3]); + robj *o; long long ll; + redisAssert(c->argv[2]->encoding == REDIS_ENCODING_RAW); + redisAssert(c->argv[3]->encoding == REDIS_ENCODING_RAW); + o = c->argv[3]; if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) { zfree(server.dbfilename); @@ -273,7 +276,6 @@ void configSetCommand(redisClient *c) { if (startAppendOnly() == REDIS_ERR) { addReplyError(c, "Unable to turn on AOF. Check server logs."); - decrRefCount(o); return; } } @@ -315,10 +317,8 @@ void configSetCommand(redisClient *c) { } else { addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", (char*)c->argv[2]->ptr); - decrRefCount(o); return; } - decrRefCount(o); addReply(c,shared.ok); return; @@ -326,14 +326,14 @@ badfmt: /* Bad format errors */ addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'", (char*)o->ptr, (char*)c->argv[2]->ptr); - decrRefCount(o); } void configGetCommand(redisClient *c) { - robj *o = getDecodedObject(c->argv[2]); + robj *o = c->argv[2]; void *replylen = addDeferredMultiBulkLength(c); char *pattern = o->ptr; int matches = 0; + redisAssert(o->encoding == REDIS_ENCODING_RAW); if (stringmatch(pattern,"dbfilename",0)) { addReplyBulkCString(c,"dbfilename"); @@ -405,12 +405,10 @@ void configGetCommand(redisClient *c) { sdsfree(buf); matches++; } - decrRefCount(o); setDeferredMultiBulkLength(c,replylen,matches*2); } void configCommand(redisClient *c) { - c->argv[c->argc-1] = tryObjectEncoding(c->argv[c->argc-1]); if (!strcasecmp(c->argv[1]->ptr,"set")) { if (c->argc != 4) goto badarity; configSetCommand(c); diff --git a/src/pubsub.c b/src/pubsub.c index 23b0ceb0b..2bd3d0580 100644 --- a/src/pubsub.c +++ b/src/pubsub.c @@ -262,7 +262,6 @@ void punsubscribeCommand(redisClient *c) { } void publishCommand(redisClient *c) { - c->argv[2] = tryObjectEncoding(c->argv[2]); int receivers = pubsubPublishMessage(c->argv[1],c->argv[2]); addReplyLongLong(c,receivers); } diff --git a/src/redis.c b/src/redis.c index a3f04f112..62a54b843 100644 --- a/src/redis.c +++ b/src/redis.c @@ -1007,7 +1007,6 @@ void pingCommand(redisClient *c) { } void echoCommand(redisClient *c) { - c->argv[1] = tryObjectEncoding(c->argv[1]); addReplyBulk(c,c->argv[1]); } From 5e78edb350c8060fa478182efc4e4aa729bbe154 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 28 Oct 2010 15:07:45 +0100 Subject: [PATCH 16/18] Unify two client flags that mean the same --- src/networking.c | 8 ++++---- src/redis.c | 4 ++-- src/redis.h | 4 +--- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/networking.c b/src/networking.c index 46d49bf6c..949537155 100644 --- a/src/networking.c +++ b/src/networking.c @@ -541,7 +541,6 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); /* Close connection after entire reply has been sent. */ - if (c->flags & REDIS_QUIT) freeClient(c); if (c->flags & REDIS_CLOSE_AFTER_REPLY) freeClient(c); } } @@ -802,9 +801,10 @@ void processInputBuffer(redisClient *c) { * will try to reiterate. The following line will make it return asap. */ if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return; - /* Never continue to process the input buffer after QUIT. After the output - * buffer is flushed (with the OK), the connection will be dropped. */ - if (c->flags & REDIS_QUIT) return; + /* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is + * written to the client. Make sure to not let the reply grow after + * this flag has been set (i.e. don't process more commands). */ + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; /* Determine request type when unknown. */ if (!c->reqtype) { diff --git a/src/redis.c b/src/redis.c index 62a54b843..5c5198239 100644 --- a/src/redis.c +++ b/src/redis.c @@ -893,10 +893,10 @@ int processCommand(redisClient *c) { * go through checking for replication and QUIT will cause trouble * when FORCE_REPLICATION is enabled and would be implemented in * a regular command proc. */ - redisAssert(!(c->flags & REDIS_QUIT)); + redisAssert(!(c->flags & REDIS_CLOSE_AFTER_REPLY)); if (!strcasecmp(c->argv[0]->ptr,"quit")) { - c->flags |= REDIS_QUIT; addReply(c,shared.ok); + c->flags |= REDIS_CLOSE_AFTER_REPLY; return REDIS_ERR; } diff --git a/src/redis.h b/src/redis.h index 1e841b73f..44857569c 100644 --- a/src/redis.h +++ b/src/redis.h @@ -144,9 +144,7 @@ #define REDIS_BLOCKED 16 /* The client is waiting in a blocking operation */ #define REDIS_IO_WAIT 32 /* The client is waiting for Virtual Memory I/O */ #define REDIS_DIRTY_CAS 64 /* Watched keys modified. EXEC will fail. */ -#define REDIS_QUIT 128 /* Client will be disconnected after reply is sent */ -#define REDIS_CLOSE_AFTER_REPLY 256 /* Close connection immediately once the - * reply has been sent. */ +#define REDIS_CLOSE_AFTER_REPLY 128 /* Close after writing entire reply. */ /* Client request types */ #define REDIS_REQ_INLINE 1 From a3a323e0e56c3d4be148fcf039cbd1887cbd1e87 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 28 Oct 2010 16:52:16 +0100 Subject: [PATCH 17/18] When REDIS_CLOSE_AFTER_REPLY is set, there may never be new replies --- src/networking.c | 5 +++++ src/redis.c | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/networking.c b/src/networking.c index 949537155..10273cc11 100644 --- a/src/networking.c +++ b/src/networking.c @@ -55,7 +55,12 @@ redisClient *createClient(int fd) { return c; } +/* Set the event loop to listen for write events on the client's socket. + * Typically gets called every time a reply is built. */ int _installWriteEvent(redisClient *c) { + /* When CLOSE_AFTER_REPLY is set, no more replies may be added! */ + redisAssert(!(c->flags & REDIS_CLOSE_AFTER_REPLY)); + if (c->fd <= 0) return REDIS_ERR; if (c->bufpos == 0 && listLength(c->reply) == 0 && (c->replstate == REDIS_REPL_NONE || diff --git a/src/redis.c b/src/redis.c index 5c5198239..1f8d71a7d 100644 --- a/src/redis.c +++ b/src/redis.c @@ -893,7 +893,6 @@ int processCommand(redisClient *c) { * go through checking for replication and QUIT will cause trouble * when FORCE_REPLICATION is enabled and would be implemented in * a regular command proc. */ - redisAssert(!(c->flags & REDIS_CLOSE_AFTER_REPLY)); if (!strcasecmp(c->argv[0]->ptr,"quit")) { addReply(c,shared.ok); c->flags |= REDIS_CLOSE_AFTER_REPLY; From 4794d88f15ee4e4175e712b411bb7cebee7aff09 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Thu, 28 Oct 2010 16:59:05 +0100 Subject: [PATCH 18/18] Rewrite comment that was no longer valid --- src/networking.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/networking.c b/src/networking.c index 10273cc11..6181799aa 100644 --- a/src/networking.c +++ b/src/networking.c @@ -798,12 +798,7 @@ int processMultibulkBuffer(redisClient *c) { void processInputBuffer(redisClient *c) { /* Keep processing while there is something in the input buffer */ while(sdslen(c->querybuf)) { - /* Before to process the input buffer, make sure the client is not - * waitig for a blocking operation such as BLPOP. Note that the first - * iteration the client is never blocked, otherwise the processInputBuffer - * would not be called at all, but after the execution of the first commands - * in the input buffer the client may be blocked, and the "goto again" - * will try to reiterate. The following line will make it return asap. */ + /* Immediately abort if the client is in the middle of something. */ if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return; /* REDIS_CLOSE_AFTER_REPLY closes the connection once the reply is