X-Git-Url: https://git.saurik.com/redis.git/blobdiff_plain/3bcffcbe5bc0acde311d0fb311f893400cd52e0e..1858da2faae3b6a8becf4f7eef3f712d6e4b986b:/src/t_list.c diff --git a/src/t_list.c b/src/t_list.c index 1c658353..ca03916b 100644 --- a/src/t_list.c +++ b/src/t_list.c @@ -86,7 +86,7 @@ unsigned long listTypeLength(robj *subject) { } /* Initialize an iterator at the specified index. */ -listTypeIterator *listTypeInitIterator(robj *subject, int index, unsigned char direction) { +listTypeIterator *listTypeInitIterator(robj *subject, long index, unsigned char direction) { listTypeIterator *li = zmalloc(sizeof(listTypeIterator)); li->subject = subject; li->encoding = subject->encoding; @@ -198,7 +198,7 @@ void listTypeInsert(listTypeEntry *entry, robj *value, int where) { int listTypeEqual(listTypeEntry *entry, robj *o) { listTypeIterator *li = entry->li; if (li->encoding == REDIS_ENCODING_ZIPLIST) { - redisAssert(o->encoding == REDIS_ENCODING_RAW); + redisAssertWithInfo(NULL,o,o->encoding == REDIS_ENCODING_RAW); return ziplistCompare(entry->zi,o->ptr,sdslen(o->ptr)); } else if (li->encoding == REDIS_ENCODING_LINKEDLIST) { return equalStringObjects(o,listNodeValue(entry->ln)); @@ -235,7 +235,7 @@ void listTypeDelete(listTypeEntry *entry) { void listTypeConvert(robj *subject, int enc) { listTypeIterator *li; listTypeEntry entry; - redisAssert(subject->type == REDIS_LIST); + redisAssertWithInfo(NULL,subject,subject->type == REDIS_LIST); if (enc == REDIS_ENCODING_LINKEDLIST) { list *l = listCreate(); @@ -259,30 +259,47 @@ void listTypeConvert(robj *subject, int enc) { *----------------------------------------------------------------------------*/ void pushGenericCommand(redisClient *c, int where) { + int j, waiting = 0, pushed = 0; robj *lobj = lookupKeyWrite(c->db,c->argv[1]); - c->argv[2] = tryObjectEncoding(c->argv[2]); - if (lobj == NULL) { - if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { - addReply(c,shared.cone); - return; + int may_have_waiting_clients = (lobj == NULL); + + if (lobj && lobj->type != REDIS_LIST) { + addReply(c,shared.wrongtypeerr); + return; + } + + for (j = 2; j < c->argc; j++) { + c->argv[j] = tryObjectEncoding(c->argv[j]); + if (may_have_waiting_clients) { + if (handleClientsWaitingListPush(c,c->argv[1],c->argv[j])) { + waiting++; + continue; + } else { + may_have_waiting_clients = 0; + } } - lobj = createZiplistObject(); - dbAdd(c->db,c->argv[1],lobj); - } else { - if (lobj->type != REDIS_LIST) { - addReply(c,shared.wrongtypeerr); - return; - } - if (handleClientsWaitingListPush(c,c->argv[1],c->argv[2])) { - signalModifiedKey(c->db,c->argv[1]); - addReply(c,shared.cone); - return; + if (!lobj) { + lobj = createZiplistObject(); + dbAdd(c->db,c->argv[1],lobj); } + listTypePush(lobj,c->argv[j],where); + pushed++; + } + addReplyLongLong(c, waiting + (lobj ? listTypeLength(lobj) : 0)); + if (pushed) signalModifiedKey(c->db,c->argv[1]); + server.dirty += pushed; + + /* Alter the replication of the command accordingly to the number of + * list elements delivered to clients waiting into a blocking operation. + * We do that only if there were waiting clients, and only if still some + * element was pushed into the list (othewise dirty is 0 and nothign will + * be propagated). */ + if (waiting && pushed) { + /* CMD KEY a b C D E */ + for (j = 0; j < waiting; j++) decrRefCount(c->argv[j+2]); + memmove(c->argv+2,c->argv+2+waiting,sizeof(robj*)*pushed); + c->argc -= waiting; } - listTypePush(lobj,c->argv[2],where); - addReplyLongLong(c,listTypeLength(lobj)); - signalModifiedKey(c->db,c->argv[1]); - server.dirty++; } void lpushCommand(redisClient *c) { @@ -305,7 +322,7 @@ void pushxGenericCommand(redisClient *c, robj *refval, robj *val, int where) { if (refval != NULL) { /* Note: we expect refval to be string-encoded because it is *not* the * last argument of the multi-bulk LINSERT. */ - redisAssert(refval->encoding == REDIS_ENCODING_RAW); + redisAssertWithInfo(c,refval,refval->encoding == REDIS_ENCODING_RAW); /* We're not sure if this value can be inserted yet, but we cannot * convert the list inside the iterator. We don't want to loop over @@ -376,9 +393,12 @@ void llenCommand(redisClient *c) { void lindexCommand(redisClient *c) { robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.nullbulk); if (o == NULL || checkType(c,o,REDIS_LIST)) return; - int index = atoi(c->argv[2]->ptr); + long index; robj *value = NULL; + if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK)) + return; + if (o->encoding == REDIS_ENCODING_ZIPLIST) { unsigned char *p; unsigned char *vstr; @@ -412,9 +432,12 @@ void lindexCommand(redisClient *c) { void lsetCommand(redisClient *c) { robj *o = lookupKeyWriteOrReply(c,c->argv[1],shared.nokeyerr); if (o == NULL || checkType(c,o,REDIS_LIST)) return; - int index = atoi(c->argv[2]->ptr); + long index; robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3])); + if ((getLongFromObjectOrReply(c, c->argv[2], &index, NULL) != REDIS_OK)) + return; + listTypeTryConversion(o,value); if (o->encoding == REDIS_ENCODING_ZIPLIST) { unsigned char *p, *zl = o->ptr; @@ -473,10 +496,10 @@ void rpopCommand(redisClient *c) { void lrangeCommand(redisClient *c) { robj *o; - int start = atoi(c->argv[2]->ptr); - int end = atoi(c->argv[3]->ptr); - int llen; - int rangelen; + long start, end, llen, rangelen; + + if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || + (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL || checkType(c,o,REDIS_LIST)) return; @@ -514,7 +537,12 @@ void lrangeCommand(redisClient *c) { p = ziplistNext(o->ptr,p); } } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) { - listNode *ln = listIndex(o->ptr,start); + listNode *ln; + + /* If we are nearest to the end of the list, reach the element + * starting from tail and going backward, as it is faster. */ + if (start > llen/2) start -= llen; + ln = listIndex(o->ptr,start); while(rangelen--) { addReplyBulk(c,ln->value); @@ -527,13 +555,13 @@ void lrangeCommand(redisClient *c) { void ltrimCommand(redisClient *c) { robj *o; - int start = atoi(c->argv[2]->ptr); - int end = atoi(c->argv[3]->ptr); - int llen; - int j, ltrim, rtrim; + long start, end, llen, j, ltrim, rtrim; list *list; listNode *ln; + if ((getLongFromObjectOrReply(c, c->argv[2], &start, NULL) != REDIS_OK) || + (getLongFromObjectOrReply(c, c->argv[3], &end, NULL) != REDIS_OK)) return; + if ((o = lookupKeyWriteOrReply(c,c->argv[1],shared.ok)) == NULL || checkType(c,o,REDIS_LIST)) return; llen = listTypeLength(o); @@ -581,10 +609,13 @@ void ltrimCommand(redisClient *c) { void lremCommand(redisClient *c) { robj *subject, *obj; obj = c->argv[3] = tryObjectEncoding(c->argv[3]); - int toremove = atoi(c->argv[2]->ptr); - int removed = 0; + long toremove; + long removed = 0; listTypeEntry entry; + if ((getLongFromObjectOrReply(c, c->argv[2], &toremove, NULL) != REDIS_OK)) + return; + subject = lookupKeyWriteOrReply(c,c->argv[1],shared.czero); if (subject == NULL || checkType(c,subject,REDIS_LIST)) return; @@ -635,19 +666,29 @@ void lremCommand(redisClient *c) { * as well. This command was originally proposed by Ezra Zygmuntowicz. */ -void rpoplpushHandlePush(redisClient *c, robj *dstkey, robj *dstobj, robj *value) { - if (!handleClientsWaitingListPush(c,dstkey,value)) { +void rpoplpushHandlePush(redisClient *origclient, redisClient *c, robj *dstkey, robj *dstobj, robj *value) { + if (!handleClientsWaitingListPush(origclient,dstkey,value)) { /* Create the list if the key does not exist */ if (!dstobj) { dstobj = createZiplistObject(); dbAdd(c->db,dstkey,dstobj); } else { signalModifiedKey(c->db,dstkey); - server.dirty++; } listTypePush(dstobj,value,REDIS_HEAD); + /* Additionally propagate this PUSH operation together with + * the operation performed by the command. */ + { + robj **argv = zmalloc(sizeof(robj*)*3); + argv[0] = createStringObject("LPUSH",5); + argv[1] = dstkey; + argv[2] = value; + incrRefCount(argv[1]); + incrRefCount(argv[2]); + alsoPropagate(server.lpushCommand,c->db->id,argv,3, + REDIS_PROPAGATE_AOF|REDIS_PROPAGATE_REPL); + } } - /* Always send the pushed value to the client. */ addReplyBulk(c,value); } @@ -658,20 +699,35 @@ void rpoplpushCommand(redisClient *c) { checkType(c,sobj,REDIS_LIST)) return; if (listTypeLength(sobj) == 0) { + /* This may only happen after loading very old RDB files. Recent + * versions of Redis delete keys of empty lists. */ addReply(c,shared.nullbulk); } else { robj *dobj = lookupKeyWrite(c->db,c->argv[2]); + robj *touchedkey = c->argv[1]; + if (dobj && checkType(c,dobj,REDIS_LIST)) return; value = listTypePop(sobj,REDIS_TAIL); - rpoplpushHandlePush(c,c->argv[2],dobj,value); + /* We saved touched key, and protect it, since rpoplpushHandlePush + * may change the client command argument vector. */ + incrRefCount(touchedkey); + rpoplpushHandlePush(c,c,c->argv[2],dobj,value); /* listTypePop returns an object with its refcount incremented */ decrRefCount(value); /* Delete the source list when it is empty */ - if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]); - signalModifiedKey(c->db,c->argv[1]); + if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey); + signalModifiedKey(c->db,touchedkey); + decrRefCount(touchedkey); server.dirty++; + + /* Replicate this as a simple RPOP since the LPUSH side is replicated + * by rpoplpushHandlePush() call if needed (it may not be needed + * if a client is blocking wait a push against the list). */ + rewriteClientCommandVector(c,2, + resetRefCount(createStringObject("RPOP",4)), + c->argv[1]); } } @@ -738,9 +794,9 @@ void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj l = listCreate(); retval = dictAdd(c->db->blocking_keys,keys[j],l); incrRefCount(keys[j]); - redisAssert(retval == DICT_OK); + redisAssertWithInfo(c,keys[j],retval == DICT_OK); } else { - l = dictGetEntryVal(de); + l = dictGetVal(de); } listAddNodeTail(l,c); } @@ -755,13 +811,13 @@ void unblockClientWaitingData(redisClient *c) { list *l; int j; - redisAssert(c->bpop.keys != NULL); + redisAssertWithInfo(c,NULL,c->bpop.keys != NULL); /* The client may wait for multiple keys, so unblock it for every key. */ for (j = 0; j < c->bpop.count; j++) { /* Remove this client from the list of clients waiting for this key. */ de = dictFind(c->db->blocking_keys,c->bpop.keys[j]); - redisAssert(de != NULL); - l = dictGetEntryVal(de); + redisAssertWithInfo(c,c->bpop.keys[j],de != NULL); + l = dictGetVal(de); listDelNode(l,listSearchKey(l,c)); /* If the list is empty we need to remove it to avoid wasting memory */ if (listLength(l) == 0) @@ -772,6 +828,7 @@ void unblockClientWaitingData(redisClient *c) { /* Cleanup the client structure */ zfree(c->bpop.keys); c->bpop.keys = NULL; + if (c->bpop.target) decrRefCount(c->bpop.target); c->bpop.target = NULL; c->flags &= ~REDIS_BLOCKED; c->flags |= REDIS_UNBLOCKED; @@ -799,7 +856,7 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) { de = dictFind(c->db->blocking_keys,key); if (de == NULL) return 0; - clients = dictGetEntryVal(de); + clients = dictGetVal(de); numclients = listLength(clients); /* Try to handle the push as long as there are clients waiting for a push. @@ -811,30 +868,32 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) { * this happens, it simply tries the next client waiting for a push. */ while (numclients--) { ln = listFirst(clients); - redisAssert(ln != NULL); + redisAssertWithInfo(c,key,ln != NULL); receiver = ln->value; dstkey = receiver->bpop.target; + /* Protect receiver->bpop.target, that will be freed by + * the next unblockClientWaitingData() call. */ + if (dstkey) incrRefCount(dstkey); + /* This should remove the first element of the "clients" list. */ unblockClientWaitingData(receiver); - redisAssert(ln != listFirst(clients)); if (dstkey == NULL) { /* BRPOP/BLPOP */ addReplyMultiBulkLen(receiver,2); addReplyBulk(receiver,key); addReplyBulk(receiver,ele); - return 1; + return 1; /* Serve just the first client as in B[RL]POP semantics */ } else { - /* BRPOPLPUSH */ + /* BRPOPLPUSH, note that receiver->db is always equal to c->db. */ dstobj = lookupKeyWrite(receiver->db,dstkey); - if (dstobj && checkType(receiver,dstobj,REDIS_LIST)) { - decrRefCount(dstkey); - } else { - rpoplpushHandlePush(receiver,dstkey,dstobj,ele); + if (!(dstobj && checkType(receiver,dstobj,REDIS_LIST))) { + rpoplpushHandlePush(c,receiver,dstkey,dstobj,ele); decrRefCount(dstkey); return 1; } + decrRefCount(dstkey); } } @@ -853,7 +912,7 @@ int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) { return REDIS_ERR; } - if (tval > 0) tval += time(NULL); + if (tval > 0) tval += server.unixtime; *timeout = tval; return REDIS_OK; @@ -876,33 +935,22 @@ void blockingPopGenericCommand(redisClient *c, int where) { return; } else { if (listTypeLength(o) != 0) { - /* If the list contains elements fall back to the usual - * non-blocking POP operation */ - robj *argv[2], **orig_argv; - int orig_argc; - - /* We need to alter the command arguments before to call - * popGenericCommand() as the command takes a single key. */ - orig_argv = c->argv; - orig_argc = c->argc; - argv[1] = c->argv[j]; - c->argv = argv; - c->argc = 2; - - /* Also the return value is different, we need to output - * the multi bulk reply header and the key name. The - * "real" command will add the last element (the value) - * for us. If this souds like an hack to you it's just - * because it is... */ - addReplyMultiBulkLen(c,2); - addReplyBulk(c,argv[1]); - - popGenericCommand(c,where); - - /* Fix the client structure with the original stuff */ - c->argv = orig_argv; - c->argc = orig_argc; + /* Non empty list, this is like a non normal [LR]POP. */ + robj *value = listTypePop(o,where); + redisAssert(value != NULL); + addReplyMultiBulkLen(c,2); + addReplyBulk(c,c->argv[j]); + addReplyBulk(c,value); + decrRefCount(value); + if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[j]); + signalModifiedKey(c->db,c->argv[j]); + server.dirty++; + + /* Replicate it as an [LR]POP instead of B[LR]POP. */ + rewriteClientCommandVector(c,2, + (where == REDIS_HEAD) ? shared.lpop : shared.rpop, + c->argv[j]); return; } } @@ -941,7 +989,7 @@ void brpoplpushCommand(redisClient *c) { /* Blocking against an empty list in a multi state * returns immediately. */ - addReply(c, shared.nullmultibulk); + addReply(c, shared.nullbulk); } else { /* The list is empty and the client blocks. */ blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]); @@ -953,7 +1001,7 @@ void brpoplpushCommand(redisClient *c) { /* The list exists and has elements, so * the regular rpoplpushCommand is executed. */ - redisAssert(listTypeLength(key) > 0); + redisAssertWithInfo(c,key,listTypeLength(key) > 0); rpoplpushCommand(c); } }