X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/9da6caac4e86aa608a3186fa8a209eed19b7b9c6..40f3ff21b7366defa4ae84ba93f4833a468669a1:/src/networking.c diff --git a/src/networking.c b/src/networking.c index e2e25207..edd7891d 100644 --- a/src/networking.c +++ b/src/networking.c @@ -14,15 +14,20 @@ redisClient *createClient(int fd) { redisClient *c = zmalloc(sizeof(redisClient)); c->bufpos = 0; - anetNonBlock(NULL,fd); - anetTcpNoDelay(NULL,fd); - if (!c) return NULL; - if (aeCreateFileEvent(server.el,fd,AE_READABLE, - readQueryFromClient, c) == AE_ERR) - { - close(fd); - zfree(c); - return NULL; + /* passing -1 as fd it is possible to create a non connected client. + * This is useful since all the Redis commands needs to be executed + * in the context of a client. When commands are executed in other + * contexts (for instance a Lua script) we need a non connected client. */ + if (fd != -1) { + anetNonBlock(NULL,fd); + anetTcpNoDelay(NULL,fd); + if (aeCreateFileEvent(server.el,fd,AE_READABLE, + readQueryFromClient, c) == AE_ERR) + { + close(fd); + zfree(c); + return NULL; + } } selectDb(c,0); @@ -31,6 +36,7 @@ redisClient *createClient(int fd) { c->reqtype = 0; c->argc = 0; c->argv = NULL; + c->cmd = NULL; c->multibulklen = 0; c->bulklen = -1; c->sentlen = 0; @@ -41,8 +47,10 @@ redisClient *createClient(int fd) { c->reply = listCreate(); listSetFreeMethod(c->reply,decrRefCount); listSetDupMethod(c->reply,dupClientReplyValue); - c->blocking_keys = NULL; - c->blocking_keys_num = 0; + c->bpop.keys = NULL; + c->bpop.count = 0; + c->bpop.timeout = 0; + c->bpop.target = NULL; c->io_keys = listCreate(); c->watched_keys = listCreate(); listSetFreeMethod(c->io_keys,decrRefCount); @@ -50,12 +58,15 @@ redisClient *createClient(int fd) { c->pubsub_patterns = listCreate(); listSetFreeMethod(c->pubsub_patterns,decrRefCount); listSetMatchMethod(c->pubsub_patterns,listMatchObjects); - listAddNodeTail(server.clients,c); + if (fd != -1) listAddNodeTail(server.clients,c); initClientMultiState(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) { + if (c->flags & REDIS_LUA_CLIENT) return REDIS_OK; if (c->fd <= 0) return REDIS_ERR; if (c->bufpos == 0 && listLength(c->reply) == 0 && (c->replstate == REDIS_REPL_NONE || @@ -81,9 +92,15 @@ robj *dupLastObjectIfNeeded(list *reply) { return listNodeValue(ln); } +/* ----------------------------------------------------------------------------- + * Low level functions to add more data to output buffers. + * -------------------------------------------------------------------------- */ + int _addReplyToBuffer(redisClient *c, char *s, size_t len) { size_t available = sizeof(c->buf)-c->bufpos; + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return REDIS_OK; + /* If there already are entries in the reply list, we cannot * add anything more to the static buffer. */ if (listLength(c->reply) > 0) return REDIS_ERR; @@ -98,6 +115,9 @@ int _addReplyToBuffer(redisClient *c, char *s, size_t len) { void _addReplyObjectToList(redisClient *c, robj *o) { robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; + if (listLength(c->reply) == 0) { incrRefCount(o); listAddNodeTail(c->reply,o); @@ -121,6 +141,12 @@ void _addReplyObjectToList(redisClient *c, robj *o) { * needed it will be free'd, otherwise it ends up in a robj. */ void _addReplySdsToList(redisClient *c, sds s) { robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) { + sdsfree(s); + return; + } + if (listLength(c->reply) == 0) { listAddNodeTail(c->reply,createObject(REDIS_STRING,s)); } else { @@ -141,6 +167,9 @@ void _addReplySdsToList(redisClient *c, sds s) { void _addReplyStringToList(redisClient *c, char *s, size_t len) { robj *tail; + + if (c->flags & REDIS_CLOSE_AFTER_REPLY) return; + if (listLength(c->reply) == 0) { listAddNodeTail(c->reply,createStringObject(s,len)); } else { @@ -158,9 +187,13 @@ void _addReplyStringToList(redisClient *c, char *s, size_t len) { } } +/* ----------------------------------------------------------------------------- + * Higher level functions to queue data on the client output buffer. + * The following functions are the ones that commands implementations will call. + * -------------------------------------------------------------------------- */ + void addReply(redisClient *c, robj *obj) { if (_installWriteEvent(c) != REDIS_OK) return; - redisAssert(!server.vm_enabled || obj->storage == REDIS_VM_MEMORY); /* This is an important place where we can avoid copy-on-write * when there is a saving child running, avoiding touching the @@ -173,6 +206,9 @@ void addReply(redisClient *c, robj *obj) { if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK) _addReplyObjectToList(c,obj); } else { + /* FIXME: convert the long into string and use _addReplyToBuffer() + * instead of calling getDecodedObject. As this place in the + * code is too performance critical. */ obj = getDecodedObject(obj); if (_addReplyToBuffer(c,obj->ptr,sdslen(obj->ptr)) != REDIS_OK) _addReplyObjectToList(c,obj); @@ -211,10 +247,17 @@ void addReplyError(redisClient *c, char *err) { } void addReplyErrorFormat(redisClient *c, const char *fmt, ...) { + size_t l, j; va_list ap; va_start(ap,fmt); sds s = sdscatvprintf(sdsempty(),fmt,ap); va_end(ap); + /* Make sure there are no newlines in the string, otherwise invalid protocol + * is emitted. */ + l = sdslen(s); + for (j = 0; j < l; j++) { + if (s[j] == '\r' || s[j] == '\n') s[j] = ' '; + } _addReplyError(c,s,sdslen(s)); sdsfree(s); } @@ -270,6 +313,7 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) { } } +/* Add a duble as a bulk reply */ void addReplyDouble(redisClient *c, double d) { char dbuf[128], sbuf[128]; int dlen, slen; @@ -278,6 +322,8 @@ void addReplyDouble(redisClient *c, double d) { addReplyString(c,sbuf,slen); } +/* Add a long long as integer reply or bulk len / multi bulk count. + * Basically this is used to output . */ void _addReplyLongLong(redisClient *c, long long ll, char prefix) { char buf[128]; int len; @@ -289,13 +335,19 @@ void _addReplyLongLong(redisClient *c, long long ll, char prefix) { } void addReplyLongLong(redisClient *c, long long ll) { - _addReplyLongLong(c,ll,':'); + if (ll == 0) + addReply(c,shared.czero); + else if (ll == 1) + addReply(c,shared.cone); + else + _addReplyLongLong(c,ll,':'); } void addReplyMultiBulkLen(redisClient *c, long length) { _addReplyLongLong(c,length,'*'); } +/* Create the length prefix of a bulk reply, example: $2234 */ void addReplyBulkLen(redisClient *c, robj *obj) { size_t len; @@ -317,47 +369,50 @@ void addReplyBulkLen(redisClient *c, robj *obj) { _addReplyLongLong(c,len,'$'); } +/* Add a Redis Object as a bulk reply */ void addReplyBulk(redisClient *c, robj *obj) { addReplyBulkLen(c,obj); addReply(c,obj); addReply(c,shared.crlf); } -/* In the CONFIG command we need to add vanilla C string as bulk replies */ +/* Add a C buffer as bulk reply */ +void addReplyBulkCBuffer(redisClient *c, void *p, size_t len) { + _addReplyLongLong(c,len,'$'); + addReplyString(c,p,len); + addReply(c,shared.crlf); +} + +/* Add a C nul term string as bulk reply */ void addReplyBulkCString(redisClient *c, char *s) { if (s == NULL) { addReply(c,shared.nullbulk); } else { - robj *o = createStringObject(s,strlen(s)); - addReplyBulk(c,o); - decrRefCount(o); + addReplyBulkCBuffer(c,s,strlen(s)); } } -void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { - int cport, cfd; - char cip[128]; - redisClient *c; - REDIS_NOTUSED(el); - REDIS_NOTUSED(mask); - REDIS_NOTUSED(privdata); +/* Add a long long as a bulk reply */ +void addReplyBulkLongLong(redisClient *c, long long ll) { + char buf[64]; + int len; - cfd = anetAccept(server.neterr, fd, cip, &cport); - if (cfd == AE_ERR) { - redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr); - return; - } - redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport); - if ((c = createClient(cfd)) == NULL) { + len = ll2string(buf,64,ll); + addReplyBulkCBuffer(c,buf,len); +} + +static void acceptCommonHandler(int fd) { + redisClient *c; + if ((c = createClient(fd)) == NULL) { redisLog(REDIS_WARNING,"Error allocating resoures for the client"); - close(cfd); /* May be already closed, just ingore errors */ + close(fd); /* May be already closed, just ingore errors */ return; } /* If maxclient directive is set and this is one client more... close the * connection. Note that we create the client instead to check before * for this condition, since now the socket is already set in nonblocking * mode and we can send an error for free using the Kernel I/O */ - if (server.maxclients && listLength(server.clients) > server.maxclients) { + if (listLength(server.clients) > server.maxclients) { char *err = "-ERR max number of clients reached\r\n"; /* That's a best effort error message, don't check write errors */ @@ -370,11 +425,44 @@ void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask) { server.stat_numconnections++; } +void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) { + int cport, cfd; + char cip[128]; + REDIS_NOTUSED(el); + REDIS_NOTUSED(mask); + REDIS_NOTUSED(privdata); + + cfd = anetTcpAccept(server.neterr, fd, cip, &cport); + if (cfd == AE_ERR) { + redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr); + return; + } + redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport); + acceptCommonHandler(cfd); +} + +void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) { + int cfd; + REDIS_NOTUSED(el); + REDIS_NOTUSED(mask); + REDIS_NOTUSED(privdata); + + cfd = anetUnixAccept(server.neterr, fd); + if (cfd == AE_ERR) { + redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr); + return; + } + redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket); + acceptCommonHandler(cfd); +} + + static void freeClientArgv(redisClient *c) { int j; for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]); c->argc = 0; + c->cmd = NULL; } void freeClient(redisClient *c) { @@ -408,24 +496,12 @@ void freeClient(redisClient *c) { ln = listSearchKey(server.clients,c); redisAssert(ln != NULL); listDelNode(server.clients,ln); - /* Remove from the list of clients waiting for swapped keys, or ready - * to be restarted, but not yet woken up again. */ - if (c->flags & REDIS_IO_WAIT) { - redisAssert(server.vm_enabled); - if (listLength(c->io_keys) == 0) { - ln = listSearchKey(server.io_ready_clients,c); - - /* When this client is waiting to be woken up (REDIS_IO_WAIT), - * it should be present in the list io_ready_clients */ - redisAssert(ln != NULL); - listDelNode(server.io_ready_clients,ln); - } else { - while (listLength(c->io_keys)) { - ln = listFirst(c->io_keys); - dontWaitForSwappedKey(c,ln->value); - } - } - server.vm_blocked_clients--; + /* When client was just unblocked because of a blocking operation, + * remove it from the list with unblocked clients. */ + if (c->flags & REDIS_UNBLOCKED) { + ln = listSearchKey(server.unblocked_clients,c); + redisAssert(ln != NULL); + listDelNode(server.unblocked_clients,ln); } listRelease(c->io_keys); /* Master/slave cleanup. @@ -443,14 +519,21 @@ void freeClient(redisClient *c) { if (c->flags & REDIS_MASTER) { server.master = NULL; server.replstate = REDIS_REPL_CONNECT; + server.repl_down_since = time(NULL); /* Since we lost the connection with the master, we should also * close the connection with all our slaves if we have any, so * when we'll resync with the master the other slaves will sync again * with us as well. Note that also when the slave is not connected - * to the master it will keep refusing connections by other slaves. */ - while (listLength(server.slaves)) { - ln = listFirst(server.slaves); - freeClient((redisClient*)ln->value); + * to the master it will keep refusing connections by other slaves. + * + * We do this only if server.masterhost != NULL. If it is NULL this + * means the user called SLAVEOF NO ONE and we are freeing our + * link with the master, so no need to close link with slaves. */ + if (server.masterhost != NULL) { + while (listLength(server.slaves)) { + ln = listFirst(server.slaves); + freeClient((redisClient*)ln->value); + } } } /* Release memory */ @@ -466,15 +549,6 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { REDIS_NOTUSED(el); REDIS_NOTUSED(mask); - /* Use writev() if we have enough buffers to send */ - if (!server.glueoutputbuf && - listLength(c->reply) > REDIS_WRITEV_THRESHOLD && - !(c->flags & REDIS_MASTER)) - { - sendReplyToClientWritev(el, fd, privdata, mask); - return; - } - while(c->bufpos > 0 || listLength(c->reply)) { if (c->bufpos > 0) { if (c->flags & REDIS_MASTER) { @@ -536,100 +610,23 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { } } if (totwritten > 0) c->lastinteraction = time(NULL); - if (listLength(c->reply) == 0) { + if (c->bufpos == 0 && listLength(c->reply) == 0) { c->sentlen = 0; aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); /* Close connection after entire reply has been sent. */ - if (c->flags & REDIS_QUIT) freeClient(c); if (c->flags & REDIS_CLOSE_AFTER_REPLY) freeClient(c); } } -void sendReplyToClientWritev(aeEventLoop *el, int fd, void *privdata, int mask) -{ - redisClient *c = privdata; - int nwritten = 0, totwritten = 0, objlen, willwrite; - robj *o; - struct iovec iov[REDIS_WRITEV_IOVEC_COUNT]; - int offset, ion = 0; - REDIS_NOTUSED(el); - REDIS_NOTUSED(mask); - - listNode *node; - while (listLength(c->reply)) { - offset = c->sentlen; - ion = 0; - willwrite = 0; - - /* fill-in the iov[] array */ - for(node = listFirst(c->reply); node; node = listNextNode(node)) { - o = listNodeValue(node); - objlen = sdslen(o->ptr); - - if (totwritten + objlen - offset > REDIS_MAX_WRITE_PER_EVENT) - break; - - if(ion == REDIS_WRITEV_IOVEC_COUNT) - break; /* no more iovecs */ - - iov[ion].iov_base = ((char*)o->ptr) + offset; - iov[ion].iov_len = objlen - offset; - willwrite += objlen - offset; - offset = 0; /* just for the first item */ - ion++; - } - - if(willwrite == 0) - break; - - /* write all collected blocks at once */ - if((nwritten = writev(fd, iov, ion)) < 0) { - if (errno != EAGAIN) { - redisLog(REDIS_VERBOSE, - "Error writing to client: %s", strerror(errno)); - freeClient(c); - return; - } - break; - } - - totwritten += nwritten; - offset = c->sentlen; - - /* remove written robjs from c->reply */ - while (nwritten && listLength(c->reply)) { - o = listNodeValue(listFirst(c->reply)); - objlen = sdslen(o->ptr); - - if(nwritten >= objlen - offset) { - listDelNode(c->reply, listFirst(c->reply)); - nwritten -= objlen - offset; - c->sentlen = 0; - } else { - /* partial write */ - c->sentlen += nwritten; - break; - } - offset = 0; - } - } - - if (totwritten > 0) - c->lastinteraction = time(NULL); - - if (listLength(c->reply) == 0) { - c->sentlen = 0; - aeDeleteFileEvent(server.el,c->fd,AE_WRITABLE); - } -} - /* resetClient prepare the client to process the next command */ void resetClient(redisClient *c) { freeClientArgv(c); c->reqtype = 0; c->multibulklen = 0; c->bulklen = -1; + /* We clear the ASKING flag as well if we are not inside a MULTI. */ + if (!(c->flags & REDIS_MULTI)) c->flags &= (~REDIS_ASKING); } void closeTimedoutClients(void) { @@ -652,7 +649,7 @@ void closeTimedoutClients(void) { redisLog(REDIS_VERBOSE,"Closing idle client"); freeClient(c); } else if (c->flags & REDIS_BLOCKED) { - if (c->blockingto != 0 && c->blockingto < now) { + if (c->bpop.timeout != 0 && c->bpop.timeout < now) { addReply(c,shared.nullmultibulk); unblockClientWaitingData(c); } @@ -703,66 +700,85 @@ static void setProtocolError(redisClient *c, int pos) { int processMultibulkBuffer(redisClient *c) { char *newline = NULL; - char *eptr; - int pos = 0, tolerr; - long bulklen; + int pos = 0, ok; + long long ll; if (c->multibulklen == 0) { /* The client should have been reset */ - redisAssert(c->argc == 0); + redisAssertWithInfo(c,NULL,c->argc == 0); /* Multi bulk length cannot be read without a \r\n */ - newline = strstr(c->querybuf,"\r\n"); + newline = strchr(c->querybuf,'\r'); if (newline == NULL) return REDIS_ERR; + /* Buffer should also contain \n */ + if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2)) + 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); + redisAssertWithInfo(c,NULL,c->querybuf[0] == '*'); + ok = string2ll(c->querybuf+1,newline-(c->querybuf+1),&ll); + if (!ok || ll > 1024*1024) { + addReplyError(c,"Protocol error: invalid multibulk length"); + setProtocolError(c,pos); + return REDIS_ERR; + } + pos = (newline-c->querybuf)+2; - if (c->multibulklen <= 0) { + if (ll <= 0) { c->querybuf = sdsrange(c->querybuf,pos,-1); return REDIS_OK; } + c->multibulklen = ll; + /* 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); + redisAssertWithInfo(c,NULL,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; - } + newline = strchr(c->querybuf+pos,'\r'); + if (newline == NULL) + break; - 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 */ + /* Buffer should also contain \n */ + if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2)) break; + + if (c->querybuf[pos] != '$') { + addReplyErrorFormat(c, + "Protocol error: expected '$', got '%c'", + c->querybuf[pos]); + setProtocolError(c,pos); + return REDIS_ERR; } + + ok = string2ll(c->querybuf+pos+1,newline-(c->querybuf+pos+1),&ll); + if (!ok || ll < 0 || ll > 512*1024*1024) { + addReplyError(c,"Protocol error: invalid bulk length"); + setProtocolError(c,pos); + return REDIS_ERR; + } + + pos += newline-(c->querybuf+pos)+2; + if (ll >= REDIS_MBULK_BIG_ARG) { + /* If we are going to read a large object from network + * try to make it likely that it will start at c->querybuf + * boundary so that we can optimized object creation + * avoiding a large copy of data. */ + c->querybuf = sdsrange(c->querybuf,pos,-1); + pos = 0; + /* Hint the sds library about the amount of bytes this string is + * going to contain. */ + c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2); + } + c->bulklen = ll; } /* Read bulk argument */ @@ -770,15 +786,32 @@ int processMultibulkBuffer(redisClient *c) { /* Not enough data (+2 == trailing \r\n) */ break; } else { - c->argv[c->argc++] = createStringObject(c->querybuf+pos,c->bulklen); - pos += c->bulklen+2; + /* Optimization: if the buffer contanins JUST our bulk element + * instead of creating a new object by *copying* the sds we + * just use the current sds string. */ + if (pos == 0 && + c->bulklen >= REDIS_MBULK_BIG_ARG && + (signed) sdslen(c->querybuf) == c->bulklen+2) + { + c->argv[c->argc++] = createObject(REDIS_STRING,c->querybuf); + sdsIncrLen(c->querybuf,-2); /* remove CRLF */ + c->querybuf = sdsempty(); + /* Assume that if we saw a fat argument we'll see another one + * likely... */ + c->querybuf = sdsMakeRoomFor(c->querybuf,c->bulklen+2); + pos = 0; + } 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); + if (pos) c->querybuf = sdsrange(c->querybuf,pos,-1); /* We're done when c->multibulk == 0 */ if (c->multibulklen == 0) { @@ -790,17 +823,13 @@ int processMultibulkBuffer(redisClient *c) { void processInputBuffer(redisClient *c) { /* Keep processing while there is something in the input buffer */ while(sdslen(c->querybuf)) { - /* Before to process the input buffer, make sure the client is not - * waitig for a blocking operation such as BLPOP. Note that the first - * iteration the client is never blocked, otherwise the processInputBuffer - * would not be called at all, but after the execution of the first commands - * in the input buffer the client may be blocked, and the "goto again" - * will try to reiterate. The following line will make it return asap. */ - if (c->flags & REDIS_BLOCKED || c->flags & REDIS_IO_WAIT) return; - - /* Never continue to process the input buffer after QUIT. After the output - * buffer is flushed (with the OK), the connection will be dropped. */ - if (c->flags & REDIS_QUIT) return; + /* Immediately abort if the client is in the middle of something. */ + if (c->flags & REDIS_BLOCKED) 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) { @@ -832,12 +861,29 @@ void processInputBuffer(redisClient *c) { void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { redisClient *c = (redisClient*) privdata; - char buf[REDIS_IOBUF_LEN]; - int nread; + int nread, readlen; + size_t qblen; REDIS_NOTUSED(el); REDIS_NOTUSED(mask); - nread = read(fd, buf, REDIS_IOBUF_LEN); + readlen = REDIS_IOBUF_LEN; + /* If this is a multi bulk request, and we are processing a bulk reply + * that is large enough, try to maximize the probabilty that the query + * buffer contains excatly the SDS string representing the object, even + * at the risk of requring more read(2) calls. This way the function + * processMultiBulkBuffer() can avoid copying buffers to create the + * Redis Object representing the argument. */ + if (c->reqtype == REDIS_REQ_MULTIBULK && c->multibulklen && c->bulklen != -1 + && c->bulklen >= REDIS_MBULK_BIG_ARG) + { + int remaining = (unsigned)(c->bulklen+2)-sdslen(c->querybuf); + + if (remaining < readlen) readlen = remaining; + } + + qblen = sdslen(c->querybuf); + c->querybuf = sdsMakeRoomFor(c->querybuf, readlen); + nread = read(fd, c->querybuf+qblen, readlen); if (nread == -1) { if (errno == EAGAIN) { nread = 0; @@ -852,10 +898,143 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { return; } if (nread) { - c->querybuf = sdscatlen(c->querybuf,buf,nread); + sdsIncrLen(c->querybuf,nread); c->lastinteraction = time(NULL); } else { return; } processInputBuffer(c); } + +void getClientsMaxBuffers(unsigned long *longest_output_list, + unsigned long *biggest_input_buffer) { + redisClient *c; + listNode *ln; + listIter li; + unsigned long lol = 0, bib = 0; + + listRewind(server.clients,&li); + while ((ln = listNext(&li)) != NULL) { + c = listNodeValue(ln); + + if (listLength(c->reply) > lol) lol = listLength(c->reply); + if (sdslen(c->querybuf) > bib) bib = sdslen(c->querybuf); + } + *longest_output_list = lol; + *biggest_input_buffer = bib; +} + +void clientCommand(redisClient *c) { + listNode *ln; + listIter li; + redisClient *client; + + if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) { + sds o = sdsempty(); + time_t now = time(NULL); + + listRewind(server.clients,&li); + while ((ln = listNext(&li)) != NULL) { + char ip[32], flags[16], *p; + int port; + + client = listNodeValue(ln); + if (anetPeerToString(client->fd,ip,&port) == -1) continue; + p = flags; + if (client->flags & REDIS_SLAVE) { + if (client->flags & REDIS_MONITOR) + *p++ = 'O'; + else + *p++ = 'S'; + } + if (client->flags & REDIS_MASTER) *p++ = 'M'; + if (p == flags) *p++ = 'N'; + if (client->flags & REDIS_MULTI) *p++ = 'x'; + if (client->flags & REDIS_BLOCKED) *p++ = 'b'; + if (client->flags & REDIS_DIRTY_CAS) *p++ = 'd'; + if (client->flags & REDIS_CLOSE_AFTER_REPLY) *p++ = 'c'; + if (client->flags & REDIS_UNBLOCKED) *p++ = 'u'; + *p++ = '\0'; + o = sdscatprintf(o, + "addr=%s:%d fd=%d idle=%ld flags=%s db=%d sub=%d psub=%d\n", + ip,port,client->fd, + (long)(now - client->lastinteraction), + flags, + client->db->id, + (int) dictSize(client->pubsub_channels), + (int) listLength(client->pubsub_patterns)); + } + addReplyBulkCBuffer(c,o,sdslen(o)); + sdsfree(o); + } else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) { + listRewind(server.clients,&li); + while ((ln = listNext(&li)) != NULL) { + char ip[32], addr[64]; + int port; + + client = listNodeValue(ln); + if (anetPeerToString(client->fd,ip,&port) == -1) continue; + snprintf(addr,sizeof(addr),"%s:%d",ip,port); + if (strcmp(addr,c->argv[2]->ptr) == 0) { + addReply(c,shared.ok); + if (c == client) { + client->flags |= REDIS_CLOSE_AFTER_REPLY; + } else { + freeClient(client); + } + return; + } + } + addReplyError(c,"No such client"); + } else { + addReplyError(c, "Syntax error, try CLIENT (LIST | KILL ip:port)"); + } +} + +/* Rewrite the command vector of the client. All the new objects ref count + * is incremented. The old command vector is freed, and the old objects + * ref count is decremented. */ +void rewriteClientCommandVector(redisClient *c, int argc, ...) { + va_list ap; + int j; + robj **argv; /* The new argument vector */ + + argv = zmalloc(sizeof(robj*)*argc); + va_start(ap,argc); + for (j = 0; j < argc; j++) { + robj *a; + + a = va_arg(ap, robj*); + argv[j] = a; + incrRefCount(a); + } + /* We free the objects in the original vector at the end, so we are + * sure that if the same objects are reused in the new vector the + * refcount gets incremented before it gets decremented. */ + for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]); + zfree(c->argv); + /* Replace argv and argc with our new versions. */ + c->argv = argv; + c->argc = argc; + c->cmd = lookupCommand(c->argv[0]->ptr); + redisAssertWithInfo(c,NULL,c->cmd != NULL); + va_end(ap); +} + +/* Rewrite a single item in the command vector. + * The new val ref count is incremented, and the old decremented. */ +void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) { + robj *oldval; + + redisAssertWithInfo(c,NULL,i < c->argc); + oldval = c->argv[i]; + c->argv[i] = newval; + incrRefCount(newval); + decrRefCount(oldval); + + /* If this is the command name make sure to fix c->cmd. */ + if (i == 0) { + c->cmd = lookupCommand(c->argv[0]->ptr); + redisAssertWithInfo(c,NULL,c->cmd != NULL); + } +}