diff --git a/src/adlist.c b/src/adlist.c index 6bde46132..66d31b168 100644 --- a/src/adlist.c +++ b/src/adlist.c @@ -139,6 +139,22 @@ list *listAddNodeTail(list *list, void *value) return list; } +/* + * Add a node that has already been allocated to the tail of list + */ +void listLinkNodeTail(list *list, listNode *node) { + if (list->len == 0) { + list->head = list->tail = node; + node->prev = node->next = NULL; + } else { + node->prev = list->tail; + node->next = NULL; + list->tail->next = node; + list->tail = node; + } + list->len++; +} + list *listInsertNode(list *list, listNode *old_node, void *value, int after) { listNode *node; diff --git a/src/adlist.h b/src/adlist.h index 8f319f7cc..7c5443769 100644 --- a/src/adlist.h +++ b/src/adlist.h @@ -90,6 +90,7 @@ void listRotateHeadToTail(list *list); void listJoin(list *l, list *o); void listInitNode(listNode *node, void *value); void listLinkNodeHead(list *list, listNode *node); +void listLinkNodeTail(list *list, listNode *node); void listUnlinkNode(list *list, listNode *node); /* Directions for iterators */ diff --git a/src/multi.c b/src/multi.c index 5249c58bf..d3a91e866 100644 --- a/src/multi.c +++ b/src/multi.c @@ -264,17 +264,38 @@ void execCommand(client *c) { * Also every client contains a list of WATCHed keys so that's possible to * un-watch such keys when the client is freed or when UNWATCH is called. */ -/* 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. This struct is also referenced from db->watched_keys dict, where the - * values are lists of watchedKey pointers. */ +/* The watchedKey struct is included in two lists: the client->watched_keys list, + * and db->watched_keys dict (each value in that dict is a list of watchedKey structs). + * The list in the client struct is a plain list, where each node's value is a pointer to a watchedKey. + * The list in the db db->watched_keys is different, the listnode member that's embedded in this struct + * is the node in the dict. And the value inside that listnode is a pointer to the that list, and we can use + * struct member offset math to get from the listnode to the watchedKey struct. + * This is done to avoid the need for listSearchKey and dictFind when we remove from the list. */ typedef struct watchedKey { + listNode node; robj *key; redisDb *db; client *client; unsigned expired:1; /* Flag that we're watching an already expired key. */ } watchedKey; +/* Attach a watchedKey to the list of clients watching that key. */ +static inline void watchedKeyLinkToClients(list *clients, watchedKey *wk) { + wk->node.value = clients; /* Point the value back to the list */ + listLinkNodeTail(clients, &wk->node); /* Link the embedded node */ +} + +/* Get the list of clients watching that key. */ +static inline list *watchedKeyGetClients(watchedKey *wk) { + return listNodeValue(&wk->node); /* embedded node->value points back to the list */ +} + +/* Get the node with wk->client in the list of clients watching that key. Actually it + * is just the embedded node. */ +static inline listNode *watchedKeyGetClientNode(watchedKey *wk) { + return &wk->node; +} + /* Watch for the specified key */ void watchForKey(client *c, robj *key) { list *clients = NULL; @@ -303,8 +324,8 @@ void watchForKey(client *c, robj *key) { wk->db = c->db; wk->expired = keyIsExpired(c->db, key); incrRefCount(key); - listAddNodeTail(c->watched_keys,wk); - listAddNodeTail(clients,wk); + listAddNodeTail(c->watched_keys, wk); + watchedKeyLinkToClients(clients, wk); } /* Unwatch all the keys watched by this client. To clean the EXEC dirty @@ -319,12 +340,11 @@ void unwatchAllKeys(client *c) { list *clients; watchedKey *wk; - /* Lookup the watched key -> clients list and remove the client's wk - * from the list */ + /* Remove the client's wk from the list of clients watching the key. */ wk = listNodeValue(ln); - clients = dictFetchValue(wk->db->watched_keys, wk->key); + clients = watchedKeyGetClients(wk); serverAssertWithInfo(c,NULL,clients != NULL); - listDelNode(clients,listSearchKey(clients,wk)); + listUnlinkNode(clients, watchedKeyGetClientNode(wk)); /* Kill the entry at all if this was the only client */ if (listLength(clients) == 0) dictDelete(wk->db->watched_keys, wk->key); @@ -367,7 +387,7 @@ void touchWatchedKey(redisDb *db, robj *key) { /* Check if we are already watching for this key */ listRewind(clients,&li); while((ln = listNext(&li))) { - watchedKey *wk = listNodeValue(ln); + watchedKey *wk = member2struct(watchedKey, node, ln); client *c = wk->client; if (wk->expired) { @@ -420,7 +440,7 @@ void touchAllWatchedKeysInDb(redisDb *emptied, redisDb *replaced_with) { if (!clients) continue; listRewind(clients,&li); while((ln = listNext(&li))) { - watchedKey *wk = listNodeValue(ln); + watchedKey *wk = member2struct(watchedKey, node, ln); if (wk->expired) { if (!replaced_with || !dictFind(replaced_with->dict, key->ptr)) { /* Expired key now deleted. No logical change. Clear the diff --git a/src/server.h b/src/server.h index 23e9d8e8e..43d3fa3a4 100644 --- a/src/server.h +++ b/src/server.h @@ -100,6 +100,13 @@ typedef struct redisObject robj; #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) +/* Offset of a member in a struct */ +#define member_offset(struct_name, member_name) ((size_t)&(((struct_name *)0)->member_name)) + +/* Get the pointer of the outer struct from a member address */ +#define member2struct(struct_name, member_name, member_addr) \ + ((struct_name *)((uint8_t*)member_addr - member_offset(struct_name, member_name))) + /* Error codes */ #define C_OK 0 #define C_ERR -1