]> git.saurik.com Git - redis.git/commitdiff
Merge branch 'master' into brpoplpush
authorPieter Noordhuis <pcnoordhuis@gmail.com>
Mon, 6 Dec 2010 11:31:56 +0000 (12:31 +0100)
committerPieter Noordhuis <pcnoordhuis@gmail.com>
Mon, 6 Dec 2010 11:31:56 +0000 (12:31 +0100)
src/networking.c
src/redis.c
src/redis.h
src/t_list.c
tests/unit/type/list.tcl

index 634e2107c0fcedb288977daf562f53bb893b8c75..7d14ac53f18bfc8c60ef457ff3b4c6383e3d59da 100644 (file)
@@ -41,8 +41,10 @@ redisClient *createClient(int fd) {
     c->reply = listCreate();
     listSetFreeMethod(c->reply,decrRefCount);
     listSetDupMethod(c->reply,dupClientReplyValue);
-    c->blocking_keys = NULL;
-    c->blocking_keys_num = 0;
+    c->bpop.keys = NULL;
+    c->bpop.count = 0;
+    c->bpop.timeout = 0;
+    c->bpop.target = NULL;
     c->io_keys = listCreate();
     c->watched_keys = listCreate();
     listSetFreeMethod(c->io_keys,decrRefCount);
@@ -677,7 +679,7 @@ void closeTimedoutClients(void) {
             redisLog(REDIS_VERBOSE,"Closing idle client");
             freeClient(c);
         } else if (c->flags & REDIS_BLOCKED) {
-            if (c->blockingto != 0 && c->blockingto < now) {
+            if (c->bpop.timeout != 0 && c->bpop.timeout < now) {
                 addReply(c,shared.nullmultibulk);
                 unblockClientWaitingData(c);
             }
index 5b39c011f537e5e4e6845e483c3b4b90449e1bc1..58a796c037e1fe9477f4487228f6f555e41fd39b 100644 (file)
@@ -89,6 +89,7 @@ struct redisCommand readonlyCommandTable[] = {
     {"rpop",rpopCommand,2,0,NULL,1,1,1},
     {"lpop",lpopCommand,2,0,NULL,1,1,1},
     {"brpop",brpopCommand,-3,0,NULL,1,1,1},
+    {"brpoplpush",brpoplpushCommand,4,REDIS_CMD_DENYOOM,NULL,1,2,1},
     {"blpop",blpopCommand,-3,0,NULL,1,1,1},
     {"llen",llenCommand,2,0,NULL,1,1,1},
     {"lindex",lindexCommand,3,0,NULL,1,1,1},
@@ -96,7 +97,7 @@ struct redisCommand readonlyCommandTable[] = {
     {"lrange",lrangeCommand,4,0,NULL,1,1,1},
     {"ltrim",ltrimCommand,4,0,NULL,1,1,1},
     {"lrem",lremCommand,4,0,NULL,1,1,1},
-    {"rpoplpush",rpoplpushcommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
+    {"rpoplpush",rpoplpushCommand,3,REDIS_CMD_DENYOOM,NULL,1,2,1},
     {"sadd",saddCommand,3,REDIS_CMD_DENYOOM,NULL,1,1,1},
     {"srem",sremCommand,3,0,NULL,1,1,1},
     {"smove",smoveCommand,4,0,NULL,1,2,1},
index 11e4e557f312a928942f2d7e9d479d463ef5abaa..f1142a5b34fc52e37d10f8adcf5ee40f251f9a71 100644 (file)
@@ -293,6 +293,16 @@ typedef struct multiState {
     int count;              /* Total number of MULTI commands */
 } multiState;
 
+typedef struct blockingState {
+    robj **keys;            /* The key we are waiting to terminate a blocking
+                             * operation such as BLPOP. Otherwise NULL. */
+    int count;              /* Number of blocking keys */
+    time_t timeout;         /* Blocking operation timeout. If UNIX current time
+                             * is >= timeout then the operation timed out. */
+    robj *target;           /* The key that should receive the element,
+                             * for BRPOPLPUSH. */
+} blockingState;
+
 /* With multiplexing we need to take per-clinet state.
  * Clients are taken in a liked list. */
 typedef struct redisClient {
@@ -316,11 +326,7 @@ typedef struct redisClient {
     long repldboff;         /* replication DB file offset */
     off_t repldbsize;       /* replication DB file size */
     multiState mstate;      /* MULTI/EXEC state */
-    robj **blocking_keys;   /* The key we are waiting to terminate a blocking
-                             * operation such as BLPOP. Otherwise NULL. */
-    int blocking_keys_num;  /* Number of blocking keys */
-    time_t blockingto;      /* Blocking operation timeout. If UNIX current time
-                             * is >= blockingto then the operation timed out. */
+    blockingState bpop;   /* blocking state */
     list *io_keys;          /* Keys this client is waiting to be loaded from the
                              * swap file in order to continue. */
     list *watched_keys;     /* Keys WATCHED for MULTI/EXEC CAS */
@@ -932,7 +938,7 @@ void flushdbCommand(redisClient *c);
 void flushallCommand(redisClient *c);
 void sortCommand(redisClient *c);
 void lremCommand(redisClient *c);
-void rpoplpushcommand(redisClient *c);
+void rpoplpushCommand(redisClient *c);
 void infoCommand(redisClient *c);
 void mgetCommand(redisClient *c);
 void monitorCommand(redisClient *c);
@@ -961,6 +967,7 @@ void execCommand(redisClient *c);
 void discardCommand(redisClient *c);
 void blpopCommand(redisClient *c);
 void brpopCommand(redisClient *c);
+void brpoplpushCommand(redisClient *c);
 void appendCommand(redisClient *c);
 void substrCommand(redisClient *c);
 void strlenCommand(redisClient *c);
index 42e1d5873eb8af400d1288aa40f9f057784a9781..ceed70c31697f26d2ab02a2eb2b39993e9c46cdb 100644 (file)
@@ -618,7 +618,7 @@ void lremCommand(redisClient *c) {
  * 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 rpoplpushCommand(redisClient *c) {
     robj *sobj, *value;
     if ((sobj = lookupKeyWriteOrReply(c,c->argv[1],shared.nullbulk)) == NULL ||
         checkType(c,sobj,REDIS_LIST)) return;
@@ -689,17 +689,23 @@ void rpoplpushcommand(redisClient *c) {
 
 /* 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 */
@@ -728,22 +734,24 @@ void unblockClientWaitingData(redisClient *c) {
     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;
+    zfree(c->bpop.keys);
+    c->bpop.keys = NULL;
+    c->bpop.target = NULL;
     c->flags &= (~REDIS_BLOCKED);
     server.blpop_blocked_clients--;
     /* We want to process data if there is some command waiting
@@ -777,27 +785,63 @@ int handleClientsWaitingListPush(redisClient *c, robj *key, robj *ele) {
     redisAssert(ln != NULL);
     receiver = ln->value;
 
-    addReplyMultiBulkLen(receiver,2);
-    addReplyBulk(receiver,key);
-    addReplyBulk(receiver,ele);
+    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;
+
+        addReplyBulk(receiver,ele);
+
+        if (!handleClientsWaitingListPush(receiver, target, ele)) {
+            /* Create the list if the key does not exist */
+            if (!dobj) {
+                dobj = createZiplistObject();
+                dbAdd(receiver->db, target, dobj);
+            }
+            listTypePush(dobj, ele, REDIS_HEAD);
+        }
+
+        decrRefCount(target);
+    }
+
     return 1;
 }
 
+int checkTimeout(redisClient *c, robj *object, time_t *timeout) {
+    long long lltimeout;
+
+    if (getLongLongFromObject(object, &lltimeout) != REDIS_OK) {
+        addReplyError(c, "timeout is not an integer");
+        return REDIS_ERR;
+    }
+
+    if (lltimeout < 0) {
+        addReplyError(c, "timeout is negative");
+        return REDIS_ERR;
+    }
+
+    *timeout = lltimeout;
+
+    return REDIS_OK;
+}
+
 /* Blocking RPOP/LPOP */
 void blockingPopGenericCommand(redisClient *c, int where) {
     robj *o;
-    long long lltimeout;
     time_t timeout;
     int j;
 
-    /* Make sure timeout is an integer value */
-    if (getLongLongFromObjectOrReply(c,c->argv[c->argc-1],&lltimeout,
-            "timeout is not an integer") != REDIS_OK) return;
-
-    /* Make sure the timeout is not negative */
-    if (lltimeout < 0) {
-        addReplyError(c,"timeout is negative");
+    if (checkTimeout(c, c->argv[c->argc - 1], &timeout) != REDIS_OK) {
         return;
     }
 
@@ -829,11 +873,13 @@ void blockingPopGenericCommand(redisClient *c, int where) {
                      * 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;
+
                     return;
                 }
             }
@@ -848,9 +894,8 @@ void blockingPopGenericCommand(redisClient *c, int where) {
     }
 
     /* If the list is empty or the key does not exists we must block */
-    timeout = lltimeout;
     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) {
@@ -860,3 +905,37 @@ void blpopCommand(redisClient *c) {
 void brpopCommand(redisClient *c) {
     blockingPopGenericCommand(c,REDIS_TAIL);
 }
+
+void brpoplpushCommand(redisClient *c) {
+    time_t timeout;
+
+    if (checkTimeout(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.nullmultibulk);
+        } else {
+            if (timeout > 0) timeout += time(NULL);
+
+            /* 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);
+        }
+    }
+}
index 4c131fc3774eb2953102dbbc5e6b89fe42288488..3616443344468d2ec998556ebae8812663c9c4ac 100644 (file)
@@ -127,8 +127,127 @@ start_server {
             assert_equal 0 [r llen blist1]
             assert_equal 1 [r llen blist2]
         }
+
+        test "BRPOPLPUSH - $type" {
+            r del target
+
+            set rd [redis_deferring_client]
+            create_$type blist "a b $large c d"
+
+            $rd brpoplpush blist target 1
+            assert_equal d [$rd read]
+
+            assert_equal d [r rpop target]
+            assert_equal "a b $large c" [r lrange blist 0 -1]
+        }
+    }
+
+    test "BRPOPLPUSH with zero timeout should block indefinitely" {
+        set rd [redis_deferring_client]
+        r del blist target
+        $rd brpoplpush blist target 0
+        after 1000
+        r rpush blist foo
+        assert_equal foo [$rd read]
+        assert_equal {foo} [r lrange target 0 -1]
+    }
+
+    test "BRPOPLPUSH with a client BLPOPing the target list" {
+        set rd [redis_deferring_client]
+        set rd2 [redis_deferring_client]
+        r del blist target
+        $rd2 blpop target 0
+        $rd brpoplpush blist target 0
+        after 1000
+        r rpush blist foo
+        assert_equal foo [$rd read]
+        assert_equal {target foo} [$rd2 read]
+        assert_equal 0 [r exists target]
+    }
+
+    test "BRPOPLPUSH with wrong source type" {
+        set rd [redis_deferring_client]
+        r del blist target
+        r set blist nolist
+        $rd brpoplpush blist target 1
+        assert_error "ERR*wrong kind*" {$rd read}
+    }
+
+    test "BRPOPLPUSH with wrong destination type" {
+        set rd [redis_deferring_client]
+        r del blist target
+        r set target nolist
+        r lpush blist foo
+        $rd brpoplpush blist target 1
+        assert_error "ERR*wrong kind*" {$rd read}
+
+        set rd [redis_deferring_client]
+        r del blist target
+        r set target nolist
+        $rd brpoplpush blist target 0
+        after 1000
+        r rpush blist foo
+        assert_error "ERR*wrong kind*" {$rd read}
+        assert_equal {foo} [r lrange blist 0 -1]
+    }
+
+    test "linked BRPOPLPUSH" {
+      set rd1 [redis_deferring_client]
+      set rd2 [redis_deferring_client]
+
+      r del list1 list2 list3
+
+      $rd1 brpoplpush list1 list2 0
+      $rd2 brpoplpush list2 list3 0
+
+      r rpush list1 foo
+
+      assert_equal {} [r lrange list1 0 -1]
+      assert_equal {} [r lrange list2 0 -1]
+      assert_equal {foo} [r lrange list3 0 -1]
+    }
+
+    test "circular BRPOPLPUSH" {
+      set rd1 [redis_deferring_client]
+      set rd2 [redis_deferring_client]
+
+      r del list1 list2
+
+      $rd1 brpoplpush list1 list2 0
+      $rd2 brpoplpush list2 list1 0
+
+      r rpush list1 foo
+
+      assert_equal {foo} [r lrange list1 0 -1]
+      assert_equal {} [r lrange list2 0 -1]
+    }
+
+    test "self-referential BRPOPLPUSH" {
+      set rd [redis_deferring_client]
+
+      r del blist
+
+      $rd brpoplpush blist blist 0
+
+      r rpush blist foo
+
+      assert_equal {foo} [r lrange blist 0 -1]
     }
 
+    test "BRPOPLPUSH inside a transaction" {
+        r del xlist target
+        r lpush xlist foo
+        r lpush xlist bar
+
+        r multi
+        r brpoplpush xlist target 0
+        r brpoplpush xlist target 0
+        r brpoplpush xlist target 0
+        r lrange xlist 0 -1
+        r lrange target 0 -1
+        r exec
+    } {foo bar {} {} {bar foo}}
+
     foreach {pop} {BLPOP BRPOP} {
         test "$pop: with single empty list argument" {
             set rd [redis_deferring_client]