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));
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();
*----------------------------------------------------------------------------*/
void pushGenericCommand(redisClient *c, int where) {
+ int j, addlen = 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;
- }
- lobj = createZiplistObject();
- dbAdd(c->db,c->argv[1],lobj);
- } else {
- if (lobj->type != REDIS_LIST) {
- addReply(c,shared.wrongtypeerr);
- 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])) {
+ addlen++;
+ continue;
+ } else {
+ may_have_waiting_clients = 0;
+ }
}
- 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++;
}
- listTypePush(lobj,c->argv[2],where);
- addReplyLongLong(c,listTypeLength(lobj));
- signalModifiedKey(c->db,c->argv[1]);
- server.dirty++;
+ addReplyLongLong(c,addlen + (lobj ? listTypeLength(lobj) : 0));
+ if (pushed) signalModifiedKey(c->db,c->argv[1]);
+ server.dirty += pushed;
}
void lpushCommand(redisClient *c) {
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
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;
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;
void lrangeCommand(redisClient *c) {
robj *o;
- int start = atoi(c->argv[2]->ptr);
- int end = atoi(c->argv[3]->ptr);
+ long start;
+ long end;
int llen;
int 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;
llen = listTypeLength(o);
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);
void ltrimCommand(redisClient *c) {
robj *o;
- int start = atoi(c->argv[2]->ptr);
- int end = atoi(c->argv[3]->ptr);
+ long start;
+ long end;
int llen;
int 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);
void lremCommand(redisClient *c) {
robj *subject, *obj;
obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
- int toremove = atoi(c->argv[2]->ptr);
+ long toremove;
int 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;
* 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) {
+ robj *aux;
+
+ 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);
+ /* If we are pushing as a result of LPUSH against a key
+ * watched by BRPOPLPUSH, we need to rewrite the command vector
+ * as an LPUSH.
+ *
+ * If this is called directly by RPOPLPUSH (either directly
+ * or via a BRPOPLPUSH where the popped list exists)
+ * we should replicate the RPOPLPUSH command itself. */
+ if (c != origclient) {
+ aux = createStringObject("LPUSH",5);
+ rewriteClientCommandVector(origclient,3,aux,dstkey,value);
+ decrRefCount(aux);
+ } else {
+ /* Make sure to always use RPOPLPUSH in the replication / AOF,
+ * even if the original command was BRPOPLPUSH. */
+ aux = createStringObject("RPOPLPUSH",9);
+ rewriteClientCommandVector(origclient,3,aux,c->argv[1],c->argv[2]);
+ decrRefCount(aux);
+ }
+ server.dirty++;
}
/* Always send the pushed value to the client. */
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++;
}
}
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);
}
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)
/* 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_BLOCKED;
+ c->flags |= REDIS_UNBLOCKED;
server.bpop_blocked_clients--;
listAddNodeTail(server.unblocked_clients,c);
}
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.
* 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);
}
}
if (listTypeLength(o) != 0) {
/* If the list contains elements fall back to the usual
* non-blocking POP operation */
+ struct redisCommand *orig_cmd;
robj *argv[2], **orig_argv;
int orig_argc;
* popGenericCommand() as the command takes a single key. */
orig_argv = c->argv;
orig_argc = c->argc;
+ orig_cmd = c->cmd;
argv[1] = c->argv[j];
c->argv = argv;
c->argc = 2;
/* Fix the client structure with the original stuff */
c->argv = orig_argv;
c->argc = orig_argc;
+ c->cmd = orig_cmd;
return;
}
/* 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]);
/* The list exists and has elements, so
* the regular rpoplpushCommand is executed. */
- redisAssert(listTypeLength(key) > 0);
+ redisAssertWithInfo(c,key,listTypeLength(key) > 0);
rpoplpushCommand(c);
}
}