X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/603e616bf4e09cdbbfeaf77a5e2a02876c229c35..db3c2a4fb4b2d4a9dd619151b7efc5e5253a8e3f:/src/networking.c diff --git a/src/networking.c b/src/networking.c index 1dab8927..3979ab62 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; @@ -52,7 +58,7 @@ 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; } @@ -60,9 +66,7 @@ redisClient *createClient(int fd) { /* 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->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 || @@ -88,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; @@ -105,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); @@ -128,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 { @@ -148,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 { @@ -165,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 @@ -221,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); } @@ -302,7 +335,12 @@ 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) { @@ -396,7 +434,7 @@ void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask) { cfd = anetTcpAccept(server.neterr, fd, cip, &cport); if (cfd == AE_ERR) { - redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr); + redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr); return; } redisLog(REDIS_VERBOSE,"Accepted %s:%d", cip, cport); @@ -411,7 +449,7 @@ void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask) { cfd = anetUnixAccept(server.neterr, fd); if (cfd == AE_ERR) { - redisLog(REDIS_VERBOSE,"Accepting client connection: %s", server.neterr); + redisLog(REDIS_WARNING,"Accepting client connection: %s", server.neterr); return; } redisLog(REDIS_VERBOSE,"Accepted connection to %s", server.unixsocket); @@ -424,6 +462,7 @@ static void freeClientArgv(redisClient *c) { for (j = 0; j < c->argc; j++) decrRefCount(c->argv[j]); c->argc = 0; + c->cmd = NULL; } void freeClient(redisClient *c) { @@ -457,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. @@ -491,16 +518,22 @@ void freeClient(redisClient *c) { /* Case 2: we lost the connection with the master. */ if (c->flags & REDIS_MASTER) { server.master = NULL; - /* FIXME */ 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 */ @@ -516,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) { @@ -586,7 +610,7 @@ 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); @@ -595,84 +619,6 @@ void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask) { } } -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); @@ -752,70 +698,74 @@ 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); /* 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); - 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) { + 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 (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); 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; + c->bulklen = ll; } /* Read bulk argument */ @@ -844,7 +794,7 @@ 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; + 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 @@ -908,3 +858,136 @@ void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask) { } 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); + redisAssert(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; + + redisAssert(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); + redisAssert(c->cmd != NULL); + } +}