Commit 21dbc649 authored by antirez's avatar antirez
Browse files

merge conflict resolved

parents 73abd0a9 4794d88f
...@@ -132,7 +132,7 @@ dep: ...@@ -132,7 +132,7 @@ dep:
$(CC) -MM *.c $(CC) -MM *.c
test: test:
(cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}") (cd ..; tclsh8.5 tests/test_helper.tcl --tags "${TAGS}" --file "${FILE}")
bench: bench:
./redis-benchmark ./redis-benchmark
......
...@@ -266,9 +266,6 @@ int loadAppendOnlyFile(char *filename) { ...@@ -266,9 +266,6 @@ int loadAppendOnlyFile(char *filename) {
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr); redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", argv[0]->ptr);
exit(1); 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 */ /* Run the command in the context of a fake client */
fakeClient->argc = argc; fakeClient->argc = argc;
fakeClient->argv = argv; fakeClient->argv = argv;
......
...@@ -246,8 +246,11 @@ loaderr: ...@@ -246,8 +246,11 @@ loaderr:
*----------------------------------------------------------------------------*/ *----------------------------------------------------------------------------*/
void configSetCommand(redisClient *c) { void configSetCommand(redisClient *c) {
robj *o = getDecodedObject(c->argv[3]); robj *o;
long long ll; 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")) { if (!strcasecmp(c->argv[2]->ptr,"dbfilename")) {
zfree(server.dbfilename); zfree(server.dbfilename);
...@@ -312,7 +315,6 @@ void configSetCommand(redisClient *c) { ...@@ -312,7 +315,6 @@ void configSetCommand(redisClient *c) {
if (startAppendOnly() == REDIS_ERR) { if (startAppendOnly() == REDIS_ERR) {
addReplyError(c, addReplyError(c,
"Unable to turn on AOF. Check server logs."); "Unable to turn on AOF. Check server logs.");
decrRefCount(o);
return; return;
} }
} }
...@@ -354,10 +356,8 @@ void configSetCommand(redisClient *c) { ...@@ -354,10 +356,8 @@ void configSetCommand(redisClient *c) {
} else { } else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
(char*)c->argv[2]->ptr); (char*)c->argv[2]->ptr);
decrRefCount(o);
return; return;
} }
decrRefCount(o);
addReply(c,shared.ok); addReply(c,shared.ok);
return; return;
...@@ -365,15 +365,15 @@ badfmt: /* Bad format errors */ ...@@ -365,15 +365,15 @@ badfmt: /* Bad format errors */
addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'", addReplyErrorFormat(c,"Invalid argument '%s' for CONFIG SET '%s'",
(char*)o->ptr, (char*)o->ptr,
(char*)c->argv[2]->ptr); (char*)c->argv[2]->ptr);
decrRefCount(o);
} }
void configGetCommand(redisClient *c) { void configGetCommand(redisClient *c) {
robj *o = getDecodedObject(c->argv[2]); robj *o = c->argv[2];
void *replylen = addDeferredMultiBulkLength(c); void *replylen = addDeferredMultiBulkLength(c);
char *pattern = o->ptr; char *pattern = o->ptr;
char buf[128]; char buf[128];
int matches = 0; int matches = 0;
redisAssert(o->encoding == REDIS_ENCODING_RAW);
if (stringmatch(pattern,"dbfilename",0)) { if (stringmatch(pattern,"dbfilename",0)) {
addReplyBulkCString(c,"dbfilename"); addReplyBulkCString(c,"dbfilename");
...@@ -462,7 +462,6 @@ void configGetCommand(redisClient *c) { ...@@ -462,7 +462,6 @@ void configGetCommand(redisClient *c) {
sdsfree(buf); sdsfree(buf);
matches++; matches++;
} }
decrRefCount(o);
setDeferredMultiBulkLength(c,replylen,matches*2); setDeferredMultiBulkLength(c,replylen,matches*2);
} }
......
...@@ -28,13 +28,11 @@ redisClient *createClient(int fd) { ...@@ -28,13 +28,11 @@ redisClient *createClient(int fd) {
selectDb(c,0); selectDb(c,0);
c->fd = fd; c->fd = fd;
c->querybuf = sdsempty(); c->querybuf = sdsempty();
c->newline = NULL; c->reqtype = 0;
c->argc = 0; c->argc = 0;
c->argv = NULL; c->argv = NULL;
c->multibulklen = 0;
c->bulklen = -1; c->bulklen = -1;
c->multibulk = 0;
c->mbargc = 0;
c->mbargv = NULL;
c->sentlen = 0; c->sentlen = 0;
c->flags = 0; c->flags = 0;
c->lastinteraction = time(NULL); c->lastinteraction = time(NULL);
...@@ -57,7 +55,12 @@ redisClient *createClient(int fd) { ...@@ -57,7 +55,12 @@ redisClient *createClient(int fd) {
return c; 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) { 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->fd <= 0) return REDIS_ERR;
if (c->bufpos == 0 && listLength(c->reply) == 0 && if (c->bufpos == 0 && listLength(c->reply) == 0 &&
(c->replstate == REDIS_REPL_NONE || (c->replstate == REDIS_REPL_NONE ||
...@@ -374,13 +377,9 @@ void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -374,13 +377,9 @@ void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
static void freeClientArgv(redisClient *c) { static void freeClientArgv(redisClient *c) {
int j; int j;
for (j = 0; j < c->argc; j++) for (j = 0; j < c->argc; j++)
decrRefCount(c->argv[j]); decrRefCount(c->argv[j]);
for (j = 0; j < c->mbargc; j++)
decrRefCount(c->mbargv[j]);
c->argc = 0; c->argc = 0;
c->mbargc = 0;
} }
void freeClient(redisClient *c) { void freeClient(redisClient *c) {
...@@ -461,7 +460,6 @@ void freeClient(redisClient *c) { ...@@ -461,7 +460,6 @@ void freeClient(redisClient *c) {
} }
/* Release memory */ /* Release memory */
zfree(c->argv); zfree(c->argv);
zfree(c->mbargv);
freeClientMultiState(c); freeClientMultiState(c);
zfree(c); zfree(c);
} }
...@@ -546,6 +544,9 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { ...@@ -546,6 +544,9 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) {
if (listLength(c->reply) == 0) { if (listLength(c->reply) == 0) {
c->sentlen = 0; c->sentlen = 0;
aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); 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) ...@@ -630,9 +631,9 @@ void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask)
/* resetClient prepare the client to process the next command */ /* resetClient prepare the client to process the next command */
void resetClient(redisClient *c) { void resetClient(redisClient *c) {
freeClientArgv(c); freeClientArgv(c);
c->reqtype = 0;
c->multibulklen = 0;
c->bulklen = -1; c->bulklen = -1;
c->multibulk = 0;
c->newline = NULL;
} }
void closeTimedoutClients(void) { void closeTimedoutClients(void) {
...@@ -663,49 +664,29 @@ void closeTimedoutClients(void) { ...@@ -663,49 +664,29 @@ void closeTimedoutClients(void) {
} }
} }
void processInputBuffer(redisClient *c) { int processInlineBuffer(redisClient *c) {
int seeknewline = 0; char *newline = strstr(c->querybuf,"\r\n");
int argc, j;
again: sds *argv;
/* 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; size_t querylen;
if (c->newline) { /* Nothing to do without a \r\n */
char *p = c->newline; if (newline == NULL)
sds query, *argv; return REDIS_ERR;
int argc, j;
c->newline = NULL; /* Split the input buffer up to the \r\n */
query = c->querybuf; querylen = newline-(c->querybuf);
c->querybuf = sdsempty(); argv = sdssplitlen(c->querybuf,querylen," ",1,&argc);
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 */ /* Leave data after the first line of the query in the buffer */
argv = sdssplitlen(query,sdslen(query)," ",1,&argc); c->querybuf = sdsrange(c->querybuf,querylen+2,-1);
sdsfree(query);
/* Setup argv array on client structure */
if (c->argv) zfree(c->argv); if (c->argv) zfree(c->argv);
c->argv = zmalloc(sizeof(robj*)*argc); c->argv = zmalloc(sizeof(robj*)*argc);
for (j = 0; j < argc; j++) { /* Create redis objects for all arguments. */
for (c->argc = 0, j = 0; j < argc; j++) {
if (sdslen(argv[j])) { if (sdslen(argv[j])) {
c->argv[c->argc] = createObject(REDIS_STRING,argv[j]); c->argv[c->argc] = createObject(REDIS_STRING,argv[j]);
c->argc++; c->argc++;
...@@ -714,39 +695,141 @@ again: ...@@ -714,39 +695,141 @@ again:
} }
} }
zfree(argv); zfree(argv);
if (c->argc) { return REDIS_OK;
/* 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. */ /* Helper function. Trims query buffer to make the function that processes
if (processCommand(c) && sdslen(c->querybuf)) goto again; * 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 { } else {
/* Nothing to process, argc == 0. Just process the query /* No newline in current buffer, so wait for more data */
* buffer if it's not empty or return to the caller */ break;
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;
} }
/* Read bulk argument */
if (sdslen(c->querybuf)-pos < (unsigned)(c->bulklen+2)) {
/* Not enough data (+2 == trailing \r\n) */
break;
} else { } else {
/* Bulk read handling. Note that if we are at this point c->argv[c->argc++] = createStringObject(c->querybuf+pos,c->bulklen);
the client already sent a command terminated with a newline, pos += c->bulklen+2;
we are reading the bulk data that is actually the last c->bulklen = -1;
argument of the command. */ c->multibulklen--;
int qbl = sdslen(c->querybuf); }
}
if (c->bulklen <= qbl) {
/* Copy everything but the final CRLF as final argument */ /* Trim to pos */
c->argv[c->argc] = createStringObject(c->querybuf,c->bulklen-2); c->querybuf = sdsrange(c->querybuf,pos,-1);
c->argc++;
c->querybuf = sdsrange(c->querybuf,c->bulklen,-1); /* We're done when c->multibulk == 0 */
/* Process the command. If the client is still valid after if (c->multibulklen == 0) {
* the processing and there is more data in the buffer return REDIS_OK;
* try to parse it. */ }
if (processCommand(c) && sdslen(c->querybuf)) goto again; return REDIS_ERR;
return; }
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 {
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) {
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) { ...@@ -773,14 +856,8 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) {
return; return;
} }
if (nread) { 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); 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 { } else {
return; return;
} }
......
...@@ -575,10 +575,28 @@ int main(int argc, char **argv) { ...@@ -575,10 +575,28 @@ int main(int argc, char **argv) {
aeMain(config.el); aeMain(config.el);
endBenchmark(); 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"); prepareForBenchmark("SET");
c = createClient(); c = createClient();
if (!c) exit(1); 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); char *data = zmalloc(config.datasize+2);
memset(data,'x',config.datasize); memset(data,'x',config.datasize);
...@@ -612,7 +630,7 @@ int main(int argc, char **argv) { ...@@ -612,7 +630,7 @@ int main(int argc, char **argv) {
prepareForBenchmark("LPUSH"); prepareForBenchmark("LPUSH");
c = createClient(); c = createClient();
if (!c) exit(1); 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); prepareClientForReply(c,REPLY_INT);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
...@@ -630,7 +648,7 @@ int main(int argc, char **argv) { ...@@ -630,7 +648,7 @@ int main(int argc, char **argv) {
prepareForBenchmark("SADD"); prepareForBenchmark("SADD");
c = createClient(); c = createClient();
if (!c) exit(1); 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); prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
...@@ -648,7 +666,7 @@ int main(int argc, char **argv) { ...@@ -648,7 +666,7 @@ int main(int argc, char **argv) {
prepareForBenchmark("LPUSH (again, in order to bench LRANGE)"); prepareForBenchmark("LPUSH (again, in order to bench LRANGE)");
c = createClient(); c = createClient();
if (!c) exit(1); 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); prepareClientForReply(c,REPLY_RETCODE);
createMissingClients(c); createMissingClients(c);
aeMain(config.el); aeMain(config.el);
......
...@@ -45,10 +45,6 @@ ...@@ -45,10 +45,6 @@
#include "zmalloc.h" #include "zmalloc.h"
#include "linenoise.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) #define REDIS_NOTUSED(V) ((void) V)
static struct config { static struct config {
......
This diff is collapsed.
...@@ -57,15 +57,15 @@ ...@@ -57,15 +57,15 @@
/* Hash table parameters */ /* Hash table parameters */
#define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */ #define REDIS_HT_MINFILL 10 /* Minimal hash table fill 10% */
/* Command flags */ /* Command flags:
#define REDIS_CMD_BULK 1 /* Bulk write command */ * REDIS_CMD_DENYOOM:
#define REDIS_CMD_INLINE 2 /* Inline command */ * Commands marked with this flag will return an error when 'maxmemory' is
/* REDIS_CMD_DENYOOM reserves a longer comment: all the commands marked with * set and the server is using more than 'maxmemory' bytes of memory.
this flags will return an error when the 'maxmemory' option is set in the * In short: commands with this flag are denied on low memory conditions.
config file and the server is using more than maxmemory bytes of memory. * REDIS_CMD_FORCE_REPLICATION:
In short this commands are denied on low memory conditions. */ * Force replication even if dirty is 0. */
#define REDIS_CMD_DENYOOM 4 #define REDIS_CMD_DENYOOM 4
#define REDIS_CMD_FORCE_REPLICATION 8 /* Force replication even if dirty is 0 */ #define REDIS_CMD_FORCE_REPLICATION 8
/* Object types */ /* Object types */
#define REDIS_STRING 0 #define REDIS_STRING 0
...@@ -144,6 +144,11 @@ ...@@ -144,6 +144,11 @@
#define REDIS_BLOCKED 16 /* The client is waiting in a blocking operation */ #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_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_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 */ /* Slave replication state - slave side */
#define REDIS_REPL_NONE 0 /* No active replication */ #define REDIS_REPL_NONE 0 /* No active replication */
...@@ -294,11 +299,11 @@ typedef struct redisClient { ...@@ -294,11 +299,11 @@ typedef struct redisClient {
redisDb *db; redisDb *db;
int dictid; int dictid;
sds querybuf; sds querybuf;
robj **argv, **mbargv; int argc;
char *newline; /* pointing to the detected newline in querybuf */ robj **argv;
int argc, mbargc; int reqtype;
long bulklen; /* bulk read len. -1 if not in bulk read mode */ int multibulklen; /* number of multi bulk arguments left to read */
int multibulk; /* multi bulk command format active */ long bulklen; /* length of bulk argument in multi bulk request */
list *reply; list *reply;
int sentlen; int sentlen;
time_t lastinteraction; /* time of the last interaction, used for timeout */ time_t lastinteraction; /* time of the last interaction, used for timeout */
......
...@@ -260,6 +260,7 @@ void listTypeConvert(robj *subject, int enc) { ...@@ -260,6 +260,7 @@ void listTypeConvert(robj *subject, int enc) {
void pushGenericCommand(redisClient *c, int where) { void pushGenericCommand(redisClient *c, int where) {
robj *lobj = lookupKeyWrite(c->db,c->argv[1]); robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
c->argv[2] = tryObjectEncoding(c->argv[2]);
if (lobj == NULL) { if (lobj == NULL) {
if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) {
addReply(c,shared.cone); addReply(c,shared.cone);
...@@ -346,14 +347,17 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { ...@@ -346,14 +347,17 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) {
} }
void lpushxCommand(redisClient *c) { void lpushxCommand(redisClient *c) {
c->argv[2] = tryObjectEncoding(c->argv[2]);
pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD); pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD);
} }
void rpushxCommand(redisClient *c) { void rpushxCommand(redisClient *c) {
c->argv[2] = tryObjectEncoding(c->argv[2]);
pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL); pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL);
} }
void linsertCommand(redisClient *c) { void linsertCommand(redisClient *c) {
c->argv[4] = tryObjectEncoding(c->argv[4]);
if (strcasecmp(c->argv[2]->ptr,"after") == 0) { if (strcasecmp(c->argv[2]->ptr,"after") == 0) {
pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL); pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL);
} else if (strcasecmp(c->argv[2]->ptr,"before") == 0) { } else if (strcasecmp(c->argv[2]->ptr,"before") == 0) {
...@@ -409,7 +413,7 @@ void lsetCommand(redisClient *c) { ...@@ -409,7 +413,7 @@ void lsetCommand(redisClient *c) {
robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr); robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr);
if (o == NULL || checkType(c,o,REDIS_LIST)) return; if (o == NULL || checkType(c,o,REDIS_LIST)) return;
int index = atoi(c->argv[2]->ptr); int index = atoi(c->argv[2]->ptr);
robj *value = c->argv[3]; robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
listTypeTryConversion(o,value); listTypeTryConversion(o,value);
if (o->encoding == REDIS_ENCODING_ZIPLIST) { if (o->encoding == REDIS_ENCODING_ZIPLIST) {
...@@ -559,7 +563,8 @@ void ltrimCommand(redisClient *c) { ...@@ -559,7 +563,8 @@ void ltrimCommand(redisClient *c) {
} }
void lremCommand(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 toremove = atoi(c->argv[2]->ptr);
int removed = 0; int removed = 0;
listTypeEntry entry; listTypeEntry entry;
......
...@@ -178,6 +178,7 @@ void saddCommand(redisClient *c) { ...@@ -178,6 +178,7 @@ void saddCommand(redisClient *c) {
robj *set; robj *set;
set = lookupKeyWrite(c->db,c->argv[1]); set = lookupKeyWrite(c->db,c->argv[1]);
c->argv[2] = tryObjectEncoding(c->argv[2]);
if (set == NULL) { if (set == NULL) {
set = setTypeCreate(c->argv[2]); set = setTypeCreate(c->argv[2]);
dbAdd(c->db,c->argv[1],set); dbAdd(c->db,c->argv[1],set);
...@@ -202,6 +203,7 @@ void sremCommand(redisClient *c) { ...@@ -202,6 +203,7 @@ void sremCommand(redisClient *c) {
if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL || if ((set = lookupKeyWriteOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return; checkType(c,set,REDIS_SET)) return;
c->argv[2] = tryObjectEncoding(c->argv[2]);
if (setTypeRemove(set,c->argv[2])) { if (setTypeRemove(set,c->argv[2])) {
if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]); if (setTypeSize(set) == 0) dbDelete(c->db,c->argv[1]);
touchWatchedKey(c->db,c->argv[1]); touchWatchedKey(c->db,c->argv[1]);
...@@ -216,7 +218,7 @@ void smoveCommand(redisClient *c) { ...@@ -216,7 +218,7 @@ void smoveCommand(redisClient *c) {
robj *srcset, *dstset, *ele; robj *srcset, *dstset, *ele;
srcset = lookupKeyWrite(c->db,c->argv[1]); srcset = lookupKeyWrite(c->db,c->argv[1]);
dstset = lookupKeyWrite(c->db,c->argv[2]); 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 the source key does not exist return 0 */
if (srcset == NULL) { if (srcset == NULL) {
...@@ -264,6 +266,7 @@ void sismemberCommand(redisClient *c) { ...@@ -264,6 +266,7 @@ void sismemberCommand(redisClient *c) {
if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL || if ((set = lookupKeyReadOrReply(c,c->argv[1],shared.czero)) == NULL ||
checkType(c,set,REDIS_SET)) return; checkType(c,set,REDIS_SET)) return;
c->argv[2] = tryObjectEncoding(c->argv[2]);
if (setTypeIsMember(set,c->argv[2])) if (setTypeIsMember(set,c->argv[2]))
addReply(c,shared.cone); addReply(c,shared.cone);
else else
......
...@@ -37,14 +37,17 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir ...@@ -37,14 +37,17 @@ void setGenericCommand(redisClient *c, int nx, robj *key, robj *val, robj *expir
} }
void setCommand(redisClient *c) { void setCommand(redisClient *c) {
c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,0,c->argv[1],c->argv[2],NULL); setGenericCommand(c,0,c->argv[1],c->argv[2],NULL);
} }
void setnxCommand(redisClient *c) { void setnxCommand(redisClient *c) {
c->argv[2] = tryObjectEncoding(c->argv[2]);
setGenericCommand(c,1,c->argv[1],c->argv[2],NULL); setGenericCommand(c,1,c->argv[1],c->argv[2],NULL);
} }
void setexCommand(redisClient *c) { void setexCommand(redisClient *c) {
c->argv[3] = tryObjectEncoding(c->argv[3]);
setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]); setGenericCommand(c,0,c->argv[1],c->argv[3],c->argv[2]);
} }
...@@ -69,6 +72,7 @@ void getCommand(redisClient *c) { ...@@ -69,6 +72,7 @@ void getCommand(redisClient *c) {
void getsetCommand(redisClient *c) { void getsetCommand(redisClient *c) {
if (getGenericCommand(c) == REDIS_ERR) return; if (getGenericCommand(c) == REDIS_ERR) return;
c->argv[2] = tryObjectEncoding(c->argv[2]);
dbReplace(c->db,c->argv[1],c->argv[2]); dbReplace(c->db,c->argv[1],c->argv[2]);
incrRefCount(c->argv[2]); incrRefCount(c->argv[2]);
touchWatchedKey(c->db,c->argv[1]); touchWatchedKey(c->db,c->argv[1]);
...@@ -180,6 +184,7 @@ void appendCommand(redisClient *c) { ...@@ -180,6 +184,7 @@ void appendCommand(redisClient *c) {
robj *o; robj *o;
o = lookupKeyWrite(c->db,c->argv[1]); o = lookupKeyWrite(c->db,c->argv[1]);
c->argv[2] = tryObjectEncoding(c->argv[2]);
if (o == NULL) { if (o == NULL) {
/* Create the key */ /* Create the key */
retval = dbAdd(c->db,c->argv[1],c->argv[2]); retval = dbAdd(c->db,c->argv[1],c->argv[2]);
......
...@@ -440,12 +440,14 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int ...@@ -440,12 +440,14 @@ void zaddGenericCommand(redisClient *c, robj *key, robj *ele, double score, int
void zaddCommand(redisClient *c) { void zaddCommand(redisClient *c) {
double scoreval; double scoreval;
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return; 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); zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,0);
} }
void zincrbyCommand(redisClient *c) { void zincrbyCommand(redisClient *c) {
double scoreval; double scoreval;
if (getDoubleFromObjectOrReply(c,c->argv[2],&scoreval,NULL) != REDIS_OK) return; 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); zaddGenericCommand(c,c->argv[1],c->argv[3],scoreval,1);
} }
...@@ -460,6 +462,7 @@ void zremCommand(redisClient *c) { ...@@ -460,6 +462,7 @@ void zremCommand(redisClient *c) {
checkType(c,zsetobj,REDIS_ZSET)) return; checkType(c,zsetobj,REDIS_ZSET)) return;
zs = zsetobj->ptr; zs = zsetobj->ptr;
c->argv[2] = tryObjectEncoding(c->argv[2]);
de = dictFind(zs->dict,c->argv[2]); de = dictFind(zs->dict,c->argv[2]);
if (de == NULL) { if (de == NULL) {
addReply(c,shared.czero); addReply(c,shared.czero);
...@@ -1004,6 +1007,7 @@ void zscoreCommand(redisClient *c) { ...@@ -1004,6 +1007,7 @@ void zscoreCommand(redisClient *c) {
checkType(c,o,REDIS_ZSET)) return; checkType(c,o,REDIS_ZSET)) return;
zs = o->ptr; zs = o->ptr;
c->argv[2] = tryObjectEncoding(c->argv[2]);
de = dictFind(zs->dict,c->argv[2]); de = dictFind(zs->dict,c->argv[2]);
if (!de) { if (!de) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
...@@ -1027,6 +1031,7 @@ void zrankGenericCommand(redisClient *c, int reverse) { ...@@ -1027,6 +1031,7 @@ void zrankGenericCommand(redisClient *c, int reverse) {
zs = o->ptr; zs = o->ptr;
zsl = zs->zsl; zsl = zs->zsl;
c->argv[2] = tryObjectEncoding(c->argv[2]);
de = dictFind(zs->dict,c->argv[2]); de = dictFind(zs->dict,c->argv[2]);
if (!de) { if (!de) {
addReply(c,shared.nullbulk); addReply(c,shared.nullbulk);
......
...@@ -36,25 +36,6 @@ array set ::redis::deferred {} ...@@ -36,25 +36,6 @@ array set ::redis::deferred {}
array set ::redis::callback {} array set ::redis::callback {}
array set ::redis::state {} ;# State in non-blocking reply reading array set ::redis::state {} ;# State in non-blocking reply reading
array set ::redis::statestack {} ;# Stack of states, for nested mbulks 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}} { proc redis {{server 127.0.0.1} {port 6379} {defer 0}} {
set fd [socket $server $port] set fd [socket $server $port]
...@@ -79,13 +60,6 @@ proc ::redis::__dispatch__ {id method args} { ...@@ -79,13 +60,6 @@ proc ::redis::__dispatch__ {id method args} {
set args [lrange $args 0 end-1] set args [lrange $args 0 end-1]
} }
if {[info command ::redis::__method__$method] eq {}} { 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" set cmd "*[expr {[llength $args]+1}]\r\n"
append cmd "$[string length $method]\r\n$method\r\n" append cmd "$[string length $method]\r\n$method\r\n"
foreach a $args { foreach a $args {
...@@ -93,11 +67,7 @@ proc ::redis::__dispatch__ {id method args} { ...@@ -93,11 +67,7 @@ proc ::redis::__dispatch__ {id method args} {
} }
::redis::redis_write $fd $cmd ::redis::redis_write $fd $cmd
flush $fd flush $fd
} else {
set cmd "$method "
append cmd [join $args]
::redis::redis_writenl $fd $cmd
}
if {!$deferred} { if {!$deferred} {
if {$blocking} { if {$blocking} {
::redis::redis_read_reply $fd ::redis::redis_read_reply $fd
...@@ -123,6 +93,14 @@ proc ::redis::__method__read {id fd} { ...@@ -123,6 +93,14 @@ proc ::redis::__method__read {id fd} {
::redis::redis_read_reply $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} { proc ::redis::__method__close {id fd} {
catch {close $fd} catch {close $fd}
catch {unset ::redis::fd($id)} catch {unset ::redis::fd($id)}
......
...@@ -215,7 +215,8 @@ proc start_server {options {code undefined}} { ...@@ -215,7 +215,8 @@ proc start_server {options {code undefined}} {
if {[dict exists $config port]} { set port [dict get $config port] } if {[dict exists $config port]} { set port [dict get $config port] }
# setup config dict # 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 "pid" $pid
dict set srv "host" $host dict set srv "host" $host
dict set srv "port" $port dict set srv "port" $port
...@@ -238,17 +239,12 @@ proc start_server {options {code undefined}} { ...@@ -238,17 +239,12 @@ proc start_server {options {code undefined}} {
after 10 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 # append the server to the stack
lappend ::servers $srv lappend ::servers $srv
# connect client (after server dict is put on the stack)
reconnect
# execute provided block # execute provided block
set curnum $::testnum set curnum $::testnum
if {![catch { uplevel 1 $code } err]} { if {![catch { uplevel 1 $code } err]} {
......
...@@ -90,8 +90,10 @@ proc test {name code {okpattern notspecified}} { ...@@ -90,8 +90,10 @@ proc test {name code {okpattern notspecified}} {
} }
} }
if {$::traceleaks} { 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 "--------- Test $::testnum LEAKED! --------"
puts $output
exit 1 exit 1
} }
} }
......
...@@ -16,6 +16,7 @@ set ::valgrind 0 ...@@ -16,6 +16,7 @@ set ::valgrind 0
set ::denytags {} set ::denytags {}
set ::allowtags {} set ::allowtags {}
set ::external 0; # If "1" this means, we are running against external instance 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 { proc execute_tests name {
source "tests/$name.tcl" source "tests/$name.tcl"
...@@ -49,6 +50,28 @@ proc r {args} { ...@@ -49,6 +50,28 @@ proc r {args} {
[srv $level "client"] {*}$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} { proc redis_deferring_client {args} {
set level 0 set level 0
if {[llength $args] > 0 && [string is integer [lindex $args 0]]} { if {[llength $args] > 0 && [string is integer [lindex $args 0]]} {
...@@ -80,8 +103,7 @@ proc cleanup {} { ...@@ -80,8 +103,7 @@ proc cleanup {} {
catch {exec rm -rf {*}[glob tests/tmp/server.*]} catch {exec rm -rf {*}[glob tests/tmp/server.*]}
} }
proc main {} { proc execute_everything {} {
cleanup
execute_tests "unit/auth" execute_tests "unit/auth"
execute_tests "unit/protocol" execute_tests "unit/protocol"
execute_tests "unit/basic" execute_tests "unit/basic"
...@@ -93,6 +115,7 @@ proc main {} { ...@@ -93,6 +115,7 @@ proc main {} {
execute_tests "unit/expire" execute_tests "unit/expire"
execute_tests "unit/other" execute_tests "unit/other"
execute_tests "unit/cas" execute_tests "unit/cas"
execute_tests "unit/quit"
execute_tests "integration/replication" execute_tests "integration/replication"
execute_tests "integration/aof" execute_tests "integration/aof"
# execute_tests "integration/redis-cli" # execute_tests "integration/redis-cli"
...@@ -110,6 +133,18 @@ proc main {} { ...@@ -110,6 +133,18 @@ proc main {} {
execute_tests "unit/expire" execute_tests "unit/expire"
execute_tests "unit/other" execute_tests "unit/other"
execute_tests "unit/cas" execute_tests "unit/cas"
}
proc main {} {
cleanup
if {[string length $::file] > 0} {
foreach {file} [split $::file ,] {
execute_tests $file
}
} else {
execute_everything
}
cleanup cleanup
puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed" puts "\n[expr $::passed+$::failed] tests, $::passed passed, $::failed failed"
...@@ -132,6 +167,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} { ...@@ -132,6 +167,9 @@ for {set j 0} {$j < [llength $argv]} {incr j} {
} }
} }
incr j incr j
} elseif {$opt eq {--file}} {
set ::file $arg
incr j
} elseif {$opt eq {--host}} { } elseif {$opt eq {--host}} {
set ::external 1 set ::external 1
set ::host $arg set ::host $arg
......
...@@ -172,7 +172,7 @@ start_server {tags {"basic"}} { ...@@ -172,7 +172,7 @@ start_server {tags {"basic"}} {
test {Commands pipelining} { test {Commands pipelining} {
set fd [r channel] 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 flush $fd
set res {} set res {}
append res [string match OK* [::redis::redis_read_reply $fd]] append res [string match OK* [::redis::redis_read_reply $fd]]
......
...@@ -123,7 +123,7 @@ start_server {tags {"other"}} { ...@@ -123,7 +123,7 @@ start_server {tags {"other"}} {
for {set i 0} {$i < 100000} {incr i} { for {set i 0} {$i < 100000} {incr i} {
set q {} set q {}
set val "0000${i}0000" 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 puts -nonewline $fd2 $q
set q {} set q {}
append q "GET key:$i\r\n" append q "GET key:$i\r\n"
......
start_server {tags {"protocol"}} { start_server {tags {"protocol"}} {
test {Handle an empty query well} { test "Handle an empty query" {
set fd [r channel] reconnect
puts -nonewline $fd "\r\n" r write "\r\n"
flush $fd r flush
r ping assert_equal "PONG" [r ping]
} {PONG} }
test {Negative multi bulk command does not create problems} { test "Negative multibulk length" {
set fd [r channel] reconnect
puts -nonewline $fd "*-10\r\n" r write "*-10\r\n"
flush $fd r flush
r ping assert_equal PONG [r ping]
} {PONG} }
test {Negative multi bulk payload} { test "Out of range multibulk length" {
set fd [r channel] reconnect
puts -nonewline $fd "SET x -10\r\n" r write "*20000000\r\n"
flush $fd r flush
gets $fd assert_error "*invalid multibulk length*" {r read}
} {*invalid bulk*} }
test {Too big bulk payload} { test "Wrong multibulk payload header" {
set fd [r channel] reconnect
puts -nonewline $fd "SET x 2000000000\r\n" r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\nfooz\r\n"
flush $fd r flush
gets $fd assert_error "*expected '$', got 'f'*" {r read}
} {*invalid bulk*count*} }
test {bulk payload is not a number} { test "Negative multibulk payload length" {
set fd [r channel] reconnect
puts -nonewline $fd "SET x blabla\r\n" r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$-10\r\n"
flush $fd r flush
gets $fd assert_error "*invalid bulk length*" {r read}
} {*invalid bulk*count*} }
test {Multi bulk request not followed by bulk args} { test "Out of range multibulk payload length" {
set fd [r channel] reconnect
puts -nonewline $fd "*1\r\nfoo\r\n" r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$2000000000\r\n"
flush $fd r flush
gets $fd assert_error "*invalid bulk length*" {r read}
} {*protocol error*} }
test {Generic wrong number of args} { test "Non-number multibulk payload length" {
catch {r ping x y z} err reconnect
set _ $err r write "*3\r\n\$3\r\nSET\r\n\$1\r\nx\r\n\$blabla\r\n"
} {*wrong*arguments*ping*} 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}
}
} }
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]
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment