*----------------------------------------------------------------------------*/
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;
+ 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;
+ }
}
- 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])) {
- touchWatchedKey(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));
- touchWatchedKey(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 (subject->encoding == REDIS_ENCODING_ZIPLIST &&
ziplistLen(subject->ptr) > server.list_max_ziplist_entries)
listTypeConvert(subject,REDIS_ENCODING_LINKEDLIST);
- touchWatchedKey(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
} else {
/* Notify client of a failed insert */
}
} else {
listTypePush(subject,val,where);
- touchWatchedKey(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
decrRefCount(value);
addReply(c,shared.ok);
- touchWatchedKey(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNodeValue(ln) = value;
incrRefCount(value);
addReply(c,shared.ok);
- touchWatchedKey(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
} else {
addReplyBulk(c,value);
decrRefCount(value);
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
- touchWatchedKey(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
}
}
void lrangeCommand(redisClient *c) {
- robj *o, *value;
+ robj *o;
int start = atoi(c->argv[2]->ptr);
int end = atoi(c->argv[3]->ptr);
int llen;
- int rangelen, j;
- listTypeEntry entry;
+ int rangelen;
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptymultibulk)) == NULL
|| checkType(c,o,REDIS_LIST)) return;
/* Return the result in form of a multi-bulk reply */
addReplyMultiBulkLen(c,rangelen);
- listTypeIterator *li = listTypeInitIterator(o,start,REDIS_TAIL);
- for (j = 0; j < rangelen; j++) {
- redisAssert(listTypeNext(li,&entry));
- value = listTypeGet(&entry);
- addReplyBulk(c,value);
- decrRefCount(value);
+ if (o->encoding == REDIS_ENCODING_ZIPLIST) {
+ unsigned char *p = ziplistIndex(o->ptr,start);
+ unsigned char *vstr;
+ unsigned int vlen;
+ long long vlong;
+
+ while(rangelen--) {
+ ziplistGet(p,&vstr,&vlen,&vlong);
+ if (vstr) {
+ addReplyBulkCBuffer(c,vstr,vlen);
+ } else {
+ addReplyBulkLongLong(c,vlong);
+ }
+ p = ziplistNext(o->ptr,p);
+ }
+ } else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
+ listNode *ln = listIndex(o->ptr,start);
+
+ while(rangelen--) {
+ addReplyBulk(c,ln->value);
+ ln = ln->next;
+ }
+ } else {
+ redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
}
- listTypeReleaseIterator(li);
}
void ltrimCommand(redisClient *c) {
redisPanic("Unknown list encoding");
}
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
- touchWatchedKey(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.ok);
}
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
addReplyLongLong(c,removed);
- if (removed) touchWatchedKey(c->db,c->argv[1]);
+ if (removed) signalModifiedKey(c->db,c->argv[1]);
}
/* This is the semantic of this command:
dstobj = createZiplistObject();
dbAdd(c->db,dstkey,dstobj);
} else {
- touchWatchedKey(c->db,dstkey);
+ signalModifiedKey(c->db,dstkey);
server.dirty++;
}
listTypePush(dstobj,value,REDIS_HEAD);
/* Delete the source list when it is empty */
if (listTypeLength(sobj) == 0) dbDelete(c->db,c->argv[1]);
- touchWatchedKey(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
}
c->bpop.target = target;
if (target != NULL) {
- incrRefCount(target);
+ incrRefCount(target);
}
for (j = 0; j < numkeys; j++) {
zfree(c->bpop.keys);
c->bpop.keys = NULL;
c->bpop.target = NULL;
- c->flags &= (~REDIS_BLOCKED);
+ c->flags &= ~REDIS_BLOCKED;
+ c->flags |= REDIS_UNBLOCKED;
server.bpop_blocked_clients--;
- /* We want to process data if there is some command waiting
- * in the input buffer. Note that this is safe even if
- * unblockClientWaitingData() gets called from freeClient() because
- * freeClient() will be smart enough to call this function
- * *after* c->querybuf was set to NULL. */
- if (c->querybuf && sdslen(c->querybuf) > 0) processInputBuffer(c);
+ listAddNodeTail(server.unblocked_clients,c);
}
/* This should be called from any function PUSHing into lists.
int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
struct dictEntry *de;
redisClient *receiver;
- list *l;
+ int numclients;
+ list *clients;
listNode *ln;
+ robj *dstkey, *dstobj;
de = dictFind(c->db->blocking_keys,key);
if (de == NULL) return 0;
- l = dictGetEntryVal(de);
- ln = listFirst(l);
- redisAssert(ln != NULL);
- receiver = ln->value;
-
- robj *target = receiver->bpop.target;
-
- unblockClientWaitingData(receiver);
-
- if (target == NULL) {
- /* BRPOP/BLPOP return a multi-bulk with the name
- * of the popped list */
- addReplyMultiBulkLen(receiver,2);
- addReplyBulk(receiver,key);
- addReplyBulk(receiver,ele);
- } else {
- /* BRPOPLPUSH */
- robj *dobj = lookupKeyWrite(receiver->db,target);
- if (dobj && checkType(receiver,dobj,REDIS_LIST)) return 0;
- rpoplpushHandlePush(receiver,target,dobj,ele);
- decrRefCount(target);
+ clients = dictGetEntryVal(de);
+ numclients = listLength(clients);
+
+ /* Try to handle the push as long as there are clients waiting for a push.
+ * Note that "numclients" is used because the list of clients waiting for a
+ * push on "key" is deleted by unblockClient() when empty.
+ *
+ * This loop will have more than 1 iteration when there is a BRPOPLPUSH
+ * that cannot push the target list because it does not contain a list. If
+ * this happens, it simply tries the next client waiting for a push. */
+ while (numclients--) {
+ ln = listFirst(clients);
+ redisAssert(ln != NULL);
+ receiver = ln->value;
+ dstkey = receiver->bpop.target;
+
+ /* This should remove the first element of the "clients" list. */
+ unblockClientWaitingData(receiver);
+
+ if (dstkey == NULL) {
+ /* BRPOP/BLPOP */
+ addReplyMultiBulkLen(receiver,2);
+ addReplyBulk(receiver,key);
+ addReplyBulk(receiver,ele);
+ return 1;
+ } else {
+ /* 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);
+ decrRefCount(dstkey);
+ return 1;
+ }
+ }
}
- return 1;
+ return 0;
}
int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) {
/* 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]);