X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/58732c23d53d6fc652668a516556ea182b0ed0f8..498dc5557c31fc9a65a36f2e8da9accfa72fbc5a:/src/networking.c?ds=inline diff --git a/src/networking.c b/src/networking.c index 862e69f4..47fbcc58 100644 --- a/src/networking.c +++ b/src/networking.c @@ -1,6 +1,8 @@ #include "redis.h" #include +static void setProtocolError(redisClient *c, int pos); + void *dupClientReplyValue(void *o) { incrRefCount((robj*)o); return o; @@ -36,7 +38,7 @@ redisClient *createClient(int fd) { c->reqtype = 0; c->argc = 0; c->argv = NULL; - c->cmd = NULL; + c->cmd = c->lastcmd = NULL; c->multibulklen = 0; c->bulklen = -1; c->sentlen = 0; @@ -45,6 +47,7 @@ redisClient *createClient(int fd) { c->authenticated = 0; c->replstate = REDIS_REPL_NONE; c->reply = listCreate(); + c->reply_bytes = 0; listSetFreeMethod(c->reply,decrRefCount); listSetDupMethod(c->reply,dupClientReplyValue); c->bpop.keys = NULL; @@ -135,6 +138,7 @@ void _addReplyObjectToList(redisClient *c, robj *o) { listAddNodeTail(c->reply,o); } } + c->reply_bytes += sdslen(o->ptr); } /* This method takes responsibility over the sds. When it is no longer @@ -147,6 +151,7 @@ void _addReplySdsToList(redisClient *c, sds s) { return; } + c->reply_bytes += sdslen(s); if (listLength(c->reply) == 0) { listAddNodeTail(c->reply,createObject(REDIS_STRING,s)); } else { @@ -185,6 +190,7 @@ void _addReplyStringToList(redisClient *c, char *s, size_t len) { listAddNodeTail(c->reply,createStringObject(s,len)); } } + c->reply_bytes += len; } /* ----------------------------------------------------------------------------- @@ -302,6 +308,7 @@ void setDeferredMultiBulkLength(redisClient *c, void *node, long length) { len = listNodeValue(ln); len->ptr = sdscatprintf(sdsempty(),"*%ld\r\n",length); + c->reply_bytes += sdslen(len->ptr); if (ln->next != NULL) { next = listNodeValue(ln->next); @@ -401,6 +408,17 @@ void addReplyBulkLongLong(redisClient *c, long long ll) { addReplyBulkCBuffer(c,buf,len); } +/* Copy 'src' client output buffers into 'dst' client output buffers. + * The function takes care of freeing the old output buffers of the + * destination client. */ +void copyClientOutputBuffer(redisClient *dst, redisClient *src) { + listRelease(dst->reply); + dst->reply = listDup(src->reply); + memcpy(dst->buf,src->buf,src->bufpos); + dst->bufpos = src->bufpos; + dst->reply_bytes = src->reply_bytes; +} + static void acceptCommonHandler(int fd) { redisClient *c; if ((c = createClient(fd)) == NULL) { @@ -419,6 +437,7 @@ static void acceptCommonHandler(int fd) { if (write(c->fd,err,strlen(err)) == -1) { /* Nothing to do, Just to avoid the warning... */ } + server.stat_rejected_conn++; freeClient(c); return; } @@ -468,6 +487,9 @@ static void freeClientArgv(redisClient *c) { void freeClient(redisClient *c) { listNode *ln; + /* If this is marked as current client unset it */ + if (server.current_client == c) server.current_client = NULL; + /* Note that if the client we are freeing is blocked into a blocking * call, we have to set querybuf to NULL *before* to call * unblockClientWaitingData() to avoid processInputBuffer() will get @@ -518,7 +540,7 @@ void freeClient(redisClient *c) { /* Case 2: we lost the connection with the master. */ if (c->flags & REDIS_MASTER) { server.master = NULL; - server.replstate = REDIS_REPL_CONNECT; + server.repl_state = 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 @@ -590,6 +612,7 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { if (c->sentlen == objlen) { listDelNode(c->reply,listFirst(c->reply)); c->sentlen = 0; + c->reply_bytes -= objlen; } } /* Note that we avoid to send more thank REDIS_MAX_WRITE_PER_EVENT @@ -664,8 +687,13 @@ int processInlineBuffer(redisClient *c) { size_t querylen; /* Nothing to do without a \r\n */ - if (newline == NULL) + if (newline == NULL) { + if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) { + addReplyError(c,"Protocol error: too big inline request"); + setProtocolError(c,0); + } return REDIS_ERR; + } /* Split the input buffer up to the \r\n */ querylen = newline-(c->querybuf); @@ -694,6 +722,12 @@ int processInlineBuffer(redisClient *c) { /* Helper function. Trims query buffer to make the function that processes * multi bulk requests idempotent. */ static void setProtocolError(redisClient *c, int pos) { + if (server.verbosity >= REDIS_VERBOSE) { + sds client = getClientInfoString(c); + redisLog(REDIS_VERBOSE, + "Protocol error from client: %s", client); + sdsfree(client); + } c->flags |= REDIS_CLOSE_AFTER_REPLY; c->querybuf = sdsrange(c->querybuf,pos,-1); } @@ -709,8 +743,13 @@ int processMultibulkBuffer(redisClient *c) { /* Multi bulk length cannot be read without a \r\n */ newline = strchr(c->querybuf,'\r'); - if (newline == NULL) + if (newline == NULL) { + if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) { + addReplyError(c,"Protocol error: too big mbulk count string"); + setProtocolError(c,0); + } return REDIS_ERR; + } /* Buffer should also contain \n */ if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2)) @@ -744,8 +783,13 @@ int processMultibulkBuffer(redisClient *c) { /* Read bulk length if unknown */ if (c->bulklen == -1) { newline = strchr(c->querybuf+pos,'\r'); - if (newline == NULL) + if (newline == NULL) { + if (sdslen(c->querybuf) > REDIS_INLINE_MAX_SIZE) { + addReplyError(c,"Protocol error: too big bulk count string"); + setProtocolError(c,0); + } break; + } /* Buffer should also contain \n */ if (newline-(c->querybuf) > ((signed)sdslen(c->querybuf)-2)) @@ -767,6 +811,17 @@ int processMultibulkBuffer(redisClient *c) { } 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; } @@ -775,20 +830,37 @@ 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) { - return REDIS_OK; - } + if (c->multibulklen == 0) return REDIS_OK; + + /* Still not read to process the command */ return REDIS_ERR; } @@ -833,12 +905,30 @@ 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); + server.current_client = c; + 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; @@ -853,12 +943,24 @@ 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 { + server.current_client = NULL; + return; + } + if (sdslen(c->querybuf) > server.client_max_querybuf_len) { + sds ci = getClientInfoString(c), bytes = sdsempty(); + + bytes = sdscatrepr(bytes,c->querybuf,64); + redisLog(REDIS_WARNING,"Closing client that reached max query buffer length: %s (qbuf initial bytes: %s)", ci, bytes); + sdsfree(ci); + sdsfree(bytes); + freeClient(c); return; } processInputBuffer(c); + server.current_client = NULL; } void getClientsMaxBuffers(unsigned long *longest_output_list, @@ -879,46 +981,81 @@ void getClientsMaxBuffers(unsigned long *longest_output_list, *biggest_input_buffer = bib; } -void clientCommand(redisClient *c) { +/* Turn a Redis client into an sds string representing its state. */ +sds getClientInfoString(redisClient *client) { + char ip[32], flags[16], events[3], *p; + int port; + time_t now = time(NULL); + int emask; + + if (anetPeerToString(client->fd,ip,&port) == -1) { + ip[0] = '?'; + ip[1] = '\0'; + port = 0; + } + 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 (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'; + if (p == flags) *p++ = 'N'; + *p++ = '\0'; + + emask = client->fd == -1 ? 0 : aeGetFileEvents(server.el,client->fd); + p = events; + if (emask & AE_READABLE) *p++ = 'r'; + if (emask & AE_WRITABLE) *p++ = 'w'; + *p = '\0'; + return sdscatprintf(sdsempty(), + "addr=%s:%d fd=%d idle=%ld flags=%s db=%d sub=%d psub=%d qbuf=%lu obl=%lu oll=%lu omem=%lu events=%s cmd=%s", + ip,port,client->fd, + (long)(now - client->lastinteraction), + flags, + client->db->id, + (int) dictSize(client->pubsub_channels), + (int) listLength(client->pubsub_patterns), + (unsigned long) sdslen(client->querybuf), + (unsigned long) client->bufpos, + (unsigned long) listLength(client->reply), + getClientOutputBufferMemoryUsage(client), + events, + client->lastcmd ? client->lastcmd->name : "NULL"); +} + +sds getAllClientsInfoString(void) { listNode *ln; listIter li; redisClient *client; + sds o = sdsempty(); - 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) { + sds cs; - listRewind(server.clients,&li); - while ((ln = listNext(&li)) != NULL) { - char ip[32], flags[16], *p; - int port; + client = listNodeValue(ln); + cs = getClientInfoString(client); + o = sdscatsds(o,cs); + sdsfree(cs); + o = sdscatlen(o,"\n",1); + } + return o; +} - 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)); - } +void clientCommand(redisClient *c) { + listNode *ln; + listIter li; + redisClient *client; + + if (!strcasecmp(c->argv[1]->ptr,"list") && c->argc == 2) { + sds o = getAllClientsInfoString(); addReplyBulkCBuffer(c,o,sdslen(o)); sdsfree(o); } else if (!strcasecmp(c->argv[1]->ptr,"kill") && c->argc == 3) { @@ -993,3 +1130,37 @@ void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) { redisAssertWithInfo(c,NULL,c->cmd != NULL); } } + +/* This function returns the number of bytes that Redis is virtually + * using to store the reply still not read by the client. + * It is "virtual" since the reply output list may contain objects that + * are shared and are not really using additional memory. + * + * The function returns the total sum of the length of all the objects + * stored in the output list, plus the memory used to allocate every + * list node. The static reply buffer is not taken into account since it + * is allocated anyway. + * + * Note: this function is very fast so can be called as many time as + * the caller wishes. The main usage of this function currently is + * enforcing the client output lenght limits. */ +unsigned long getClientOutputBufferMemoryUsage(redisClient *c) { + unsigned long list_item_size = sizeof(listNode); + + return c->reply_bytes + (list_item_size*listLength(c->reply)); +} + +/* Get the class of a client, used in order to envorce limits to different + * classes of clients. + * + * The function will return one of the following: + * REDIS_CLIENT_LIMIT_CLASS_NORMAL -> Normal client + * REDIS_CLIENT_LIMIT_CLASS_SLAVE -> Slave or client executing MONITOR command + * REDIS_CLIENT_LIMIT_CLASS_PUBSUB -> Client subscribed to Pub/Sub channels + */ +int getClientLimitClass(redisClient *c) { + if (c->flags & REDIS_SLAVE) return REDIS_CLIENT_LIMIT_CLASS_SLAVE; + if (dictSize(c->pubsub_channels) || listLength(c->pubsub_patterns)) + return REDIS_CLIENT_LIMIT_CLASS_PUBSUB; + return REDIS_CLIENT_LIMIT_CLASS_NORMAL; +}