* 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++;
}
}
/* 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;
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, 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;
}