diff options
Diffstat (limited to 'src/multi.c')
-rw-r--r-- | src/multi.c | 63 |
1 files changed, 52 insertions, 11 deletions
diff --git a/src/multi.c b/src/multi.c index a98195672..11f33f48f 100644 --- a/src/multi.c +++ b/src/multi.c @@ -189,7 +189,7 @@ void execCommand(client *c) { c->argc = c->mstate.commands[j].argc; c->argv = c->mstate.commands[j].argv; c->argv_len = c->mstate.commands[j].argv_len; - c->cmd = c->mstate.commands[j].cmd; + c->cmd = c->realcmd = c->mstate.commands[j].cmd; /* ACL permissions are also checked at the time of execution in case * they were changed after the commands were queued. */ @@ -240,7 +240,7 @@ void execCommand(client *c) { c->argv = orig_argv; c->argv_len = orig_argv_len; c->argc = orig_argc; - c->cmd = orig_cmd; + c->cmd = c->realcmd = orig_cmd; discardTransaction(c); server.in_exec = 0; @@ -257,10 +257,13 @@ void execCommand(client *c) { /* In the client->watched_keys list we need to use watchedKey structures * as in order to identify a key in Redis we need both the key name and the - * DB */ + * DB. This struct is also referenced from db->watched_keys dict, where the + * values are lists of watchedKey pointers. */ typedef struct watchedKey { robj *key; redisDb *db; + client *client; + unsigned expired:1; /* Flag that we're watching an already expired key. */ } watchedKey; /* Watch for the specified key */ @@ -284,13 +287,15 @@ void watchForKey(client *c, robj *key) { dictAdd(c->db->watched_keys,key,clients); incrRefCount(key); } - listAddNodeTail(clients,c); /* Add the new key to the list of keys watched by this client */ wk = zmalloc(sizeof(*wk)); wk->key = key; + wk->client = c; wk->db = c->db; + wk->expired = keyIsExpired(c->db, key); incrRefCount(key); listAddNodeTail(c->watched_keys,wk); + listAddNodeTail(clients,wk); } /* Unwatch all the keys watched by this client. To clean the EXEC dirty @@ -305,12 +310,12 @@ void unwatchAllKeys(client *c) { list *clients; watchedKey *wk; - /* Lookup the watched key -> clients list and remove the client + /* Lookup the watched key -> clients list and remove the client's wk * from the list */ wk = listNodeValue(ln); clients = dictFetchValue(wk->db->watched_keys, wk->key); serverAssertWithInfo(c,NULL,clients != NULL); - listDelNode(clients,listSearchKey(clients,c)); + listDelNode(clients,listSearchKey(clients,wk)); /* Kill the entry at all if this was the only client */ if (listLength(clients) == 0) dictDelete(wk->db->watched_keys, wk->key); @@ -321,8 +326,8 @@ void unwatchAllKeys(client *c) { } } -/* iterates over the watched_keys list and - * look for an expired key . */ +/* Iterates over the watched_keys list and looks for an expired key. Keys which + * were expired already when WATCH was called are ignored. */ int isWatchedKeyExpired(client *c) { listIter li; listNode *ln; @@ -331,6 +336,7 @@ int isWatchedKeyExpired(client *c) { listRewind(c->watched_keys,&li); while ((ln = listNext(&li))) { wk = listNodeValue(ln); + if (wk->expired) continue; /* was expired when WATCH was called */ if (keyIsExpired(wk->db, wk->key)) return 1; } @@ -352,13 +358,31 @@ void touchWatchedKey(redisDb *db, robj *key) { /* Check if we are already watching for this key */ listRewind(clients,&li); while((ln = listNext(&li))) { - client *c = listNodeValue(ln); + watchedKey *wk = listNodeValue(ln); + client *c = wk->client; + + if (wk->expired) { + /* The key was already expired when WATCH was called. */ + if (db == wk->db && + equalStringObjects(key, wk->key) && + dictFind(db->dict, key->ptr) == NULL) + { + /* Already expired key is deleted, so logically no change. Clear + * the flag. Deleted keys are not flagged as expired. */ + wk->expired = 0; + goto skip_client; + } + break; + } c->flags |= CLIENT_DIRTY_CAS; /* As the client is marked as dirty, there is no point in getting here * again in case that key (or others) are modified again (or keep the * memory overhead till EXEC). */ unwatchAllKeys(c); + + skip_client: + continue; } } @@ -379,14 +403,31 @@ void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) { dictIterator *di = dictGetSafeIterator(emptied->watched_keys); while((de = dictNext(di)) != NULL) { robj *key = dictGetKey(de); - if (dictFind(emptied->dict, key->ptr) || + int exists_in_emptied = dictFind(emptied->dict, key->ptr) != NULL; + if (exists_in_emptied || (replaced_with && dictFind(replaced_with->dict, key->ptr))) { list *clients = dictGetVal(de); if (!clients) continue; listRewind(clients,&li); while((ln = listNext(&li))) { - client *c = listNodeValue(ln); + watchedKey *wk = listNodeValue(ln); + if (wk->expired) { + if (!replaced_with || !dictFind(replaced_with->dict, key->ptr)) { + /* Expired key now deleted. No logical change. Clear the + * flag. Deleted keys are not flagged as expired. */ + wk->expired = 0; + continue; + } else if (keyIsExpired(replaced_with, key)) { + /* Expired key remains expired. */ + continue; + } + } else if (!exists_in_emptied && keyIsExpired(replaced_with, key)) { + /* Non-existing key is replaced with an expired key. */ + wk->expired = 1; + continue; + } + client *c = wk->client; c->flags |= CLIENT_DIRTY_CAS; /* As the client is marked as dirty, there is no point in getting here * again for others keys (or keep the memory overhead till EXEC). */ |