*----------------------------------------------------------------------------*/
void pushGenericCommand(redisClient *c, int where) {
+ int j, addlen = 0, pushed = 0;
robj *lobj = lookupKeyWrite(c->db,c->argv[1]);
- 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])) {
- 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));
- 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);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
} else {
/* Notify client of a failed insert */
}
} else {
listTypePush(subject,val,where);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
- addReplyUlong(c,listTypeLength(subject));
+ addReplyLongLong(c,listTypeLength(subject));
}
void lpushxCommand(redisClient *c) {
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
pushxGenericCommand(c,NULL,c->argv[2],REDIS_HEAD);
}
void rpushxCommand(redisClient *c) {
+ c->argv[2] = tryObjectEncoding(c->argv[2]);
pushxGenericCommand(c,NULL,c->argv[2],REDIS_TAIL);
}
void linsertCommand(redisClient *c) {
+ c->argv[4] = tryObjectEncoding(c->argv[4]);
if (strcasecmp(c->argv[2]->ptr,"after") == 0) {
pushxGenericCommand(c,c->argv[3],c->argv[4],REDIS_TAIL);
} else if (strcasecmp(c->argv[2]->ptr,"before") == 0) {
void llenCommand(redisClient *c) {
robj *o = lookupKeyReadOrReply(c,c->argv[1],shared.czero);
if (o == NULL || checkType(c,o,REDIS_LIST)) return;
- addReplyUlong(c,listTypeLength(o));
+ addReplyLongLong(c,listTypeLength(o));
}
void lindexCommand(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);
- robj *value = c->argv[3];
+ robj *value = (c->argv[3] = tryObjectEncoding(c->argv[3]));
listTypeTryConversion(o,value);
if (o->encoding == REDIS_ENCODING_ZIPLIST) {
o->ptr = ziplistInsert(o->ptr,p,value->ptr,sdslen(value->ptr));
decrRefCount(value);
addReply(c,shared.ok);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
} else if (o->encoding == REDIS_ENCODING_LINKEDLIST) {
listNodeValue(ln) = value;
incrRefCount(value);
addReply(c,shared.ok);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
}
} else {
addReplyBulk(c,value);
decrRefCount(value);
if (listTypeLength(o) == 0) dbDelete(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;
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
- if (end < 0) end = 0;
- /* indexes sanity checks */
+ /* Invariant: start >= 0, so this test will be true when end < 0.
+ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
- /* Out of range start or start > end result in empty list */
addReply(c,shared.emptymultibulk);
return;
}
rangelen = (end-start)+1;
/* Return the result in form of a multi-bulk reply */
- addReplySds(c,sdscatprintf(sdsempty(),"*%d\r\n",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);
+ addReplyMultiBulkLen(c,rangelen);
+ 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;
+
+ /* 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);
+ ln = ln->next;
+ }
+ } else {
+ redisPanic("List encoding is not LINKEDLIST nor ZIPLIST!");
}
- listTypeReleaseIterator(li);
}
void ltrimCommand(redisClient *c) {
if (start < 0) start = llen+start;
if (end < 0) end = llen+end;
if (start < 0) start = 0;
- if (end < 0) end = 0;
- /* indexes sanity checks */
+ /* Invariant: start >= 0, so this test will be true when end < 0.
+ * The range is empty when start > end or start >= length. */
if (start > end || start >= llen) {
/* Out of range start or start > end result in empty list */
ltrim = llen;
redisPanic("Unknown list encoding");
}
if (listTypeLength(o) == 0) dbDelete(c->db,c->argv[1]);
+ signalModifiedKey(c->db,c->argv[1]);
server.dirty++;
addReply(c,shared.ok);
}
void lremCommand(redisClient *c) {
- robj *subject, *obj = c->argv[3];
+ robj *subject, *obj;
+ obj = c->argv[3] = tryObjectEncoding(c->argv[3]);
int toremove = atoi(c->argv[2]->ptr);
int removed = 0;
listTypeEntry entry;
decrRefCount(obj);
if (listTypeLength(subject) == 0) dbDelete(c->db,c->argv[1]);
- addReplySds(c,sdscatprintf(sdsempty(),":%d\r\n",removed));
+ addReplyLongLong(c,removed);
+ if (removed) signalModifiedKey(c->db,c->argv[1]);
}
/* This is the semantic of this command:
* RPOPLPUSH srclist dstlist:
- * IF LLEN(srclist) > 0
- * element = RPOP srclist
- * LPUSH dstlist element
- * RETURN element
- * ELSE
- * RETURN nil
- * END
+ * IF LLEN(srclist) > 0
+ * element = RPOP srclist
+ * LPUSH dstlist element
+ * RETURN element
+ * ELSE
+ * RETURN nil
+ * END
* END
*
* The idea is to be able to get an element from a list in a reliable way
* since the element is not just returned but pushed against another list
* as well. This command was originally proposed by Ezra Zygmuntowicz.
*/
-void rpoplpushcommand(redisClient *c) {
+
+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);
+ }
+ 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. */
+ addReplyBulk(c,value);
+}
+
+void rpoplpushCommand(redisClient *c) {
robj *sobj, *value;
if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
checkType(c,sobj,REDIS_LIST)) return;
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);
-
- /* Add the element to the target list (unless it's directly
- * passed to some BLPOP-ing client */
- if (!handleClientsWaitingListPush(c,c->argv[2],value)) {
- /* Create the list if the key does not exist */
- if (!dobj) {
- dobj = createZiplistObject();
- dbAdd(c->db,c->argv[2],dobj);
- }
- listTypePush(dobj,value,REDIS_HEAD);
- }
-
- /* Send the element to the client as reply as well */
- addReplyBulk(c,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]);
+ if (listTypeLength(sobj) == 0) dbDelete(c->db,touchedkey);
+ signalModifiedKey(c->db,touchedkey);
+ decrRefCount(touchedkey);
server.dirty++;
}
}
/* Set a client in blocking mode for the specified key, with the specified
* timeout */
-void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout) {
+void blockForKeys(redisClient *c, robj **keys, int numkeys, time_t timeout, robj *target) {
dictEntry *de;
list *l;
int j;
- c->blocking_keys = zmalloc(sizeof(robj*)*numkeys);
- c->blocking_keys_num = numkeys;
- c->blockingto = timeout;
+ c->bpop.keys = zmalloc(sizeof(robj*)*numkeys);
+ c->bpop.count = numkeys;
+ c->bpop.timeout = timeout;
+ c->bpop.target = target;
+
+ if (target != NULL) {
+ incrRefCount(target);
+ }
+
for (j = 0; j < numkeys; j++) {
/* Add the key in the client structure, to map clients -> keys */
- c->blocking_keys[j] = keys[j];
+ c->bpop.keys[j] = keys[j];
incrRefCount(keys[j]);
/* And in the other "side", to map keys -> clients */
}
/* Mark the client as a blocked client */
c->flags |= REDIS_BLOCKED;
- server.blpop_blocked_clients++;
+ server.bpop_blocked_clients++;
}
/* Unblock a client that's waiting in a blocking operation such as BLPOP */
list *l;
int j;
- redisAssert(c->blocking_keys != NULL);
+ redisAssert(c->bpop.keys != NULL);
/* The client may wait for multiple keys, so unblock it for every key. */
- for (j = 0; j < c->blocking_keys_num; j++) {
+ 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->blocking_keys[j]);
+ de = dictFind(c->db->blocking_keys,c->bpop.keys[j]);
redisAssert(de != NULL);
l = dictGetEntryVal(de);
listDelNode(l,listSearchKey(l,c));
/* If the list is empty we need to remove it to avoid wasting memory */
if (listLength(l) == 0)
- dictDelete(c->db->blocking_keys,c->blocking_keys[j]);
- decrRefCount(c->blocking_keys[j]);
+ dictDelete(c->db->blocking_keys,c->bpop.keys[j]);
+ decrRefCount(c->bpop.keys[j]);
}
+
/* Cleanup the client structure */
- zfree(c->blocking_keys);
- c->blocking_keys = NULL;
- c->flags &= (~REDIS_BLOCKED);
- server.blpop_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);
+ 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;
+ server.bpop_blocked_clients--;
+ 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;
+ 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;
+
+ /* 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);
+
+ if (dstkey == NULL) {
+ /* BRPOP/BLPOP */
+ addReplyMultiBulkLen(receiver,2);
+ addReplyBulk(receiver,key);
+ addReplyBulk(receiver,ele);
+ return 1; /* Serve just the first client as in B[RL]POP semantics */
+ } else {
+ /* BRPOPLPUSH, note that receiver->db is always equal to c->db. */
+ dstobj = lookupKeyWrite(receiver->db,dstkey);
+ if (!(dstobj && checkType(receiver,dstobj,REDIS_LIST))) {
+ rpoplpushHandlePush(c,receiver,dstkey,dstobj,ele);
+ decrRefCount(dstkey);
+ return 1;
+ }
+ decrRefCount(dstkey);
+ }
+ }
- addReplySds(receiver,sdsnew("*2\r\n"));
- addReplyBulk(receiver,key);
- addReplyBulk(receiver,ele);
- unblockClientWaitingData(receiver);
- return 1;
+ return 0;
+}
+
+int getTimeoutFromObjectOrReply(redisClient *c, robj *object, time_t *timeout) {
+ long tval;
+
+ if (getLongFromObjectOrReply(c,object,&tval,
+ "timeout is not an integer or out of range") != REDIS_OK)
+ return REDIS_ERR;
+
+ if (tval < 0) {
+ addReplyError(c,"timeout is negative");
+ return REDIS_ERR;
+ }
+
+ if (tval > 0) tval += time(NULL);
+ *timeout = tval;
+
+ return REDIS_OK;
}
/* Blocking RPOP/LPOP */
time_t timeout;
int j;
+ if (getTimeoutFromObjectOrReply(c,c->argv[c->argc-1],&timeout) != REDIS_OK)
+ return;
+
for (j = 1; j < c->argc-1; j++) {
o = lookupKeyWrite(c->db,c->argv[j]);
if (o != NULL) {
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;
* "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... */
- addReplySds(c,sdsnew("*2\r\n"));
+ 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;
+ c->cmd = orig_cmd;
+
return;
}
}
}
}
+
+ /* If we are inside a MULTI/EXEC and the list is empty the only thing
+ * we can do is treating it as a timeout (even with timeout 0). */
+ if (c->flags & REDIS_MULTI) {
+ addReply(c,shared.nullmultibulk);
+ return;
+ }
+
/* If the list is empty or the key does not exists we must block */
- timeout = strtol(c->argv[c->argc-1]->ptr,NULL,10);
- if (timeout > 0) timeout += time(NULL);
- blockForKeys(c,c->argv+1,c->argc-2,timeout);
+ blockForKeys(c, c->argv + 1, c->argc - 2, timeout, NULL);
}
void blpopCommand(redisClient *c) {
void brpopCommand(redisClient *c) {
blockingPopGenericCommand(c,REDIS_TAIL);
}
+
+void brpoplpushCommand(redisClient *c) {
+ time_t timeout;
+
+ if (getTimeoutFromObjectOrReply(c,c->argv[3],&timeout) != REDIS_OK)
+ return;
+
+ robj *key = lookupKeyWrite(c->db, c->argv[1]);
+
+ if (key == NULL) {
+ if (c->flags & REDIS_MULTI) {
+
+ /* Blocking against an empty list in a multi state
+ * returns immediately. */
+ addReply(c, shared.nullbulk);
+ } else {
+ /* The list is empty and the client blocks. */
+ blockForKeys(c, c->argv + 1, 1, timeout, c->argv[2]);
+ }
+ } else {
+ if (key->type != REDIS_LIST) {
+ addReply(c, shared.wrongtypeerr);
+ } else {
+
+ /* The list exists and has elements, so
+ * the regular rpoplpushCommand is executed. */
+ redisAssert(listTypeLength(key) > 0);
+ rpoplpushCommand(c);
+ }
+ }
+}