]> git.saurik.com Git - redis.git/commitdiff
merge conflict resolved
authorantirez <antirez@gmail.com>
Thu, 28 Oct 2010 20:59:47 +0000 (22:59 +0200)
committerantirez <antirez@gmail.com>
Thu, 28 Oct 2010 20:59:47 +0000 (22:59 +0200)
21 files changed:
src/Makefile
src/aof.c
src/config.c
src/networking.c
src/redis-benchmark.c
src/redis-cli.c
src/redis.c
src/redis.h
src/t_list.c
src/t_set.c
src/t_string.c
src/t_zset.c
tests/support/redis.tcl
tests/support/server.tcl
tests/support/test.tcl
tests/test_helper.tcl
tests/unit/basic.tcl
tests/unit/other.tcl
tests/unit/protocol.tcl
tests/unit/quit.tcl [new file with mode: 0644]
tests/unit/sort.tcl

index 6f09200e1cd0aede8fbd443f838bebd2cac6aa40..ba6ecf4d26ac205c5a3a52f8dd9a202dc2082635 100644 (file)
@@ -132,7 +132,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
index f7a0c4532966d73dfe79e3124d0712c726b27887..4dbce394c821930cab09da3a79ed0b8c26187626 100644 (file)
--- 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;
index 9655917cd49058d9de960706a2fdf0fefb0f2146..db58a2360b4817f0576ae8e5e48b9230e9514698 100644 (file)
@@ -246,8 +246,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);
@@ -312,7 +315,6 @@ void configSetCommand(redisClient *c) {
                 if (startAppendOnly() == REDIS_ERR) {
                     addReplyError(c,
                         "Unable to turn on AOF. Check server logs.");
-                    decrRefCount(o);
                     return;
                 }
             }
@@ -354,10 +356,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;
 
@@ -365,15 +365,15 @@ 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;
     char buf[128];
     int matches = 0;
+    redisAssert(o->encoding == REDIS_ENCODING_RAW);
 
     if (stringmatch(pattern,"dbfilename",0)) {
         addReplyBulkCString(c,"dbfilename");
@@ -462,7 +462,6 @@ void configGetCommand(redisClient *c) {
         sdsfree(buf);
         matches++;
     }
-    decrRefCount(o);
     setDeferredMultiBulkLength(c,replylen,matches*2);
 }
 
index 632fd04769dca5a3c464b26b982d8a9a064a4faa..6181799aabf191660858356dcd8d773add450374 100644 (file)
@@ -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);
@@ -57,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 ||
@@ -374,13 +377,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 +460,6 @@ void freeClient(redisClient *c) {
     }
     /* Release memory */
     zfree(c->argv);
-    zfree(c->mbargv);
     freeClientMultiState(c);
     zfree(c);
 }
@@ -546,6 +544,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_CLOSE_AFTER_REPLY) freeClient(c);
     }
 }
 
@@ -630,9 +631,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) {
@@ -663,90 +664,172 @@ void closeTimedoutClients(void) {
     }
 }
 
-void processInputBuffer(redisClient *c) {
-    int seeknewline = 0;
-
-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;
-
-    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;
-
-        if (c->newline) {
-            char *p = c->newline;
-            sds query, *argv;
-            int argc, j;
-
-            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]);
+int processInlineBuffer(redisClient *c) {
+    char *newline = strstr(c->querybuf,"\r\n");
+    int argc, j;
+    sds *argv;
+    size_t querylen;
+
+    /* Nothing to do without a \r\n */
+    if (newline == NULL)
+        return REDIS_ERR;
+
+    /* Split the input buffer up to the \r\n */
+    querylen = newline-(c->querybuf);
+    argv = sdssplitlen(c->querybuf,querylen," ",1,&argc);
+
+    /* Leave data after the first line of the query in the buffer */
+    c->querybuf = sdsrange(c->querybuf,querylen+2,-1);
+
+    /* Setup argv array on client structure */
+    if (c->argv) zfree(c->argv);
+    c->argv = zmalloc(sizeof(robj*)*argc);
+
+    /* 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++;
+        } 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;
+        } 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 */
+        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;
             }
-            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;
+        }
+
+        /* 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)) {
+        /* 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
+         * 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) {
+            if (c->querybuf[0] == '*') {
+                c->reqtype = REDIS_REQ_MULTIBULK;
             } 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;
+                c->reqtype = REDIS_REQ_INLINE;
             }
-            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);
-            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;
+
+        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) {
+            resetClient(c);
+        } else {
+            /* Only reset the client when the command was executed. */
+            if (processCommand(c) == REDIS_OK)
+                resetClient(c);
         }
     }
 }
@@ -773,14 +856,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;
     }
index 297ecc6c5a87ab28cc8f135fcd7842de4c26f4d4..c5ababf2ad3c03d9f4edf95eabe41e3a965fc782 100644 (file)
@@ -575,10 +575,28 @@ 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);
-        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 +630,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 +648,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 +666,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);
index 5071604b871112b23b0acdb3a7abd40881dc89b1..aa7306b449f70b3d6fa5bdc9cfe921c7d885a0bb 100644 (file)
 #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 {
index 08bba542a86474affa6237a7b1aa6a89397b58c9..4aa19560040096ac48b733fbedef57016ab112ba 100644 (file)
@@ -69,120 +69,120 @@ 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},
-    {"zrevrangebyscore",zrevrangebyscoreCommand,-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},
+    {"zrevrangebyscore",zrevrangebyscoreCommand,-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 ============================ */
@@ -899,84 +899,14 @@ 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 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. */
     if (!strcasecmp(c->argv[0]->ptr,"quit")) {
-        freeClient(c);
-        return 0;
+        addReply(c,shared.ok);
+        c->flags |= REDIS_CLOSE_AFTER_REPLY;
+        return REDIS_ERR;
     }
 
     /* Now lookup the command and check ASAP about trivial error conditions
@@ -985,55 +915,18 @@ 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]);
 
     /* 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.
@@ -1046,8 +939,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 */
@@ -1056,8 +948,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 */
@@ -1069,13 +960,10 @@ 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);
     }
-
-    /* Prepare the client for the next command */
-    resetClient(c);
-    return 1;
+    return REDIS_OK;
 }
 
 /*================================== Shutdown =============================== */
index bc961a71a2007b46559c7d70f8f6fd0362b190b8..cf21447d4005a8ca7362380266efd1c92de5553e 100644 (file)
 /* 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
 #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_CLOSE_AFTER_REPLY 128 /* Close after writing entire reply. */
+
+/* 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 */
@@ -294,11 +299,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 */
index 41d651f64543d144bc528f312adf2f61d63f7372..42e1d5873eb8af400d1288aa40f9f057784a9781 100644 (file)
@@ -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;
index e2ac5ae5378155f329f76d90d7f5c6680e959f05..234efc7de6291fa90b9e913f9c10961502715864 100644 (file)
@@ -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
index 509c630a49540163980ef3aba678a6d9efbb9623..39ee506d5e9586851ad8bf603859ca2c0b2d15b7 100644 (file)
@@ -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]);
index d45e93691bb01b3fa8a88fddab3e643f0758ba0a..8139b53d84bb43b94ea7455786eac7ee54403003 100644 (file)
@@ -440,12 +440,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);
 }
 
@@ -460,6 +462,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);
@@ -1004,6 +1007,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);
@@ -1027,6 +1031,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);
index 7c7c65c6564f2cb1847d695b53d35a0f78c685f5..4f8ac485dc69ffb29127927f8280c1a874cad4ec 100644 (file)
@@ -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
@@ -123,6 +93,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)}
index e5ca6c6cd69ffb664990c314bbcde90211c0d794..1507088e002e68d4d70e1627177a1a3b529b8f89 100644 (file)
@@ -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]} {
index 93f64928e11b234092ed6bbab33a1c85f9a7b6ef..e801e1f228ea91946dd8d68ae4bfd722bcdcf52a 100644 (file)
@@ -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
         }
     }
index ee7fa3e19597205b0757e667fdb3cf74a401e3d3..4c207f643cf8abfc23b3817754b85fc73d732378 100644 (file)
@@ -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"
@@ -49,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]]} {
@@ -80,8 +103,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"
@@ -93,6 +115,7 @@ proc main {} {
     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"
@@ -110,6 +133,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 +167,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
index a8f7feb0bddc5dda70f9488d1d6fa6ee3afd87aa..4c6662c67defb0cd5b313eae2cb5bf75638c4ee8 100644 (file)
@@ -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]]
index 5967c722dd5fc5563d1642c14daa9e3ca5d07d79..2e6c0ae1723bd6a9e3fce3dd5febebd6f7aef427 100644 (file)
@@ -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"
index 5bf42d7feeed375b33d27efd81dbf5eef2d4d38d..b0faf5dd74ed0dfe33f7620203560d5b52983fb8 100644 (file)
@@ -1,48 +1,62 @@
 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 {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 multi bulk payload} {
-        set fd [r channel]
-        puts -nonewline $fd "SET x -10\r\n"
-        flush $fd
-        gets $fd
-    } {*invalid bulk*}
-
-    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 {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 {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 {Generic wrong number of args} {
-        catch {r ping x y z} err
-        set _ $err
-    } {*wrong*arguments*ping*}
+    test "Handle an empty query" {
+        reconnect
+        r write "\r\n"
+        r flush
+        assert_equal "PONG" [r ping]
+    }
+
+    test "Negative multibulk length" {
+        reconnect
+        r write "*-10\r\n"
+        r flush
+        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"
+        r flush
+        assert_error "*expected '$', got 'f'*" {r read}
+    }
+
+    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 "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 "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 "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/quit.tcl b/tests/unit/quit.tcl
new file mode 100644 (file)
index 0000000..4cf440a
--- /dev/null
@@ -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]
+
+    }
+}
index dcc471fb5ba2580beb705490bdfe41c109814514..415585223b47a16a1b78f0998d69fe9d5fd28982 100644 (file)
@@ -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 <const>, 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 "