X-Git-Url: http://git.liburcu.org/?a=blobdiff_plain;f=urcu-ht.c;h=a26878fd347ccf22294509960fcf95a6cc67f1d3;hb=2e6070d0f3c445476172dda329ae5d2a981a4986;hp=8edfae331440abcae8f566015e56464d77fedb0b;hpb=ab7d5fc6ff3128b5699e1871e9726f4e79b8f2e6;p=urcu.git diff --git a/urcu-ht.c b/urcu-ht.c index 8edfae3..a26878f 100644 --- a/urcu-ht.c +++ b/urcu-ht.c @@ -1,4 +1,8 @@ +/* + * TODO: keys are currently assumed <= sizeof(void *). Key target never freed. + */ + #define _LGPL_SOURCE #include #include @@ -9,6 +13,8 @@ #include #include #include +#include +#include struct rcu_ht_node; @@ -19,68 +25,41 @@ struct rcu_ht_node { }; struct rcu_ht { - struct rcu_ht_node *tbl[HASH_SIZE]; + struct rcu_ht_node **tbl; ht_hash_fct hash_fct; void (*free_fct)(void *data); /* fct to free data */ + uint32_t keylen; + uint32_t hashseed; + struct ht_size { + unsigned long add; + unsigned long lookup; + } size; }; -struct rcu_ht *ht_new(ht_hash_fct hash_fct, void (*free_fct)(void *data)) +struct rcu_ht *ht_new(ht_hash_fct hash_fct, void (*free_fct)(void *data), + unsigned long init_size, uint32_t keylen, + uint32_t hashseed) { struct rcu_ht *ht; ht = calloc(1, sizeof(struct rcu_ht)); ht->hash_fct = hash_fct; ht->free_fct = free_fct; + ht->size.add = init_size; + ht->size.lookup = init_size; + ht->keylen = keylen; + ht->hashseed = hashseed; + ht->tbl = calloc(init_size, sizeof(struct rcu_ht_node *)); return ht; } -/* delete all elements */ -void ht_delete_all(struct rcu_ht *ht) -{ - unsigned long i; - struct rcu_ht_node **prev, *node; - - for (i = 0; i < HASH_SIZE; i++) { - rcu_read_lock(); - - prev = &ht->tbl[i]; - node = rcu_dereference(*prev); - /* - * Cut the head, therefore whole bucket will be unused - * after GP. (except for concurrent additions) - */ - rcu_assign_pointer(prev, NULL); - for (;;) { - if (likely(!node)) { - break; - } - prev = &node->next; - if (node->data) - call_rcu(ht->free_fct, node->data); - call_rcu(free, node); - node = rcu_dereference(*prev); - } - rcu_read_unlock(); - } -} - -/* - * Should only be called when no more concurrent readers nor writers can - * possibly access the table. - */ -void ht_destroy(struct rcu_ht *ht) -{ - ht_delete_all(ht); - free(ht); -} - void *ht_lookup(struct rcu_ht *ht, void *key) { unsigned long hash; struct rcu_ht_node *node; void *ret; - hash = ht->hash_fct(key); + hash = ht->hash_fct(key, ht->keylen, ht->hashseed) % ht->size.lookup; rcu_read_lock(); node = rcu_dereference(ht->tbl[hash]); @@ -114,8 +93,6 @@ int ht_add(struct rcu_ht *ht, void *key, void *data) new_head = calloc(1, sizeof(struct rcu_ht_node)); new_head->key = key; new_head->data = data; - hash = ht->hash_fct(key); - /* here comes the fun and tricky part. * Add at the beginning with a cmpxchg. * Hold a read lock between the moment the first element is read @@ -127,6 +104,8 @@ int ht_add(struct rcu_ht *ht, void *key, void *data) retry: rcu_read_lock(); + hash = ht->hash_fct(key, ht->keylen, ht->hashseed) % ht->size.add; + old_head = node = rcu_dereference(ht->tbl[hash]); for (;;) { if (likely(!node)) { @@ -138,6 +117,7 @@ retry: } node = rcu_dereference(node->next); } + new_head->next = old_head; if (rcu_cmpxchg_pointer(&ht->tbl[hash], old_head, new_head) != old_head) goto restart; end: @@ -154,24 +134,30 @@ restart: /* * Restart until we successfully remove the entry, or no entry is left * ((void *)(unsigned long)-ENOENT). + * Deal with concurrent stealers by verifying that there are no element + * in the list still pointing to the element stolen. (del_node) */ void *ht_steal(struct rcu_ht *ht, void *key) { - struct rcu_ht_node **prev, *node; + struct rcu_ht_node **prev, *node, *del_node = NULL; unsigned long hash; void *data; - hash = ht->hash_fct(key); - retry: rcu_read_lock(); + hash = ht->hash_fct(key, ht->keylen, ht->hashseed) % ht->size.lookup; + prev = &ht->tbl[hash]; node = rcu_dereference(*prev); for (;;) { if (likely(!node)) { - data = (void *)(unsigned long)-ENOENT; - goto end; + if (del_node) { + goto end; + } else { + data = (void *)(unsigned long)-ENOENT; + goto error; + } } if (node->key == key) { break; @@ -181,13 +167,22 @@ retry: } /* Found it ! pointer to object is in "prev" */ if (rcu_cmpxchg_pointer(prev, node, node->next) != node) - goto restart; + del_node = node; + goto restart; + end: + /* + * From that point, we own node. Note that there can still be concurrent + * RCU readers using it. We can free it outside of read lock after a GP. + */ rcu_read_unlock(); data = node->data; call_rcu(free, node); + return data; +error: + rcu_read_unlock(); return data; /* restart loop, release and re-take the read lock to be kind to GP */ @@ -201,8 +196,8 @@ int ht_delete(struct rcu_ht *ht, void *key) void *data; data = ht_steal(ht, key); - if (data) { - if (ht->free_fct && data) + if (data && data != (void *)(unsigned long)-ENOENT) { + if (ht->free_fct) call_rcu(ht->free_fct, data); return 0; } else { @@ -210,7 +205,85 @@ int ht_delete(struct rcu_ht *ht, void *key) } } -unsigned long stupid_hash(void *key) +/* Delete all old elements. Allow concurrent writer accesses. */ +int ht_delete_all(struct rcu_ht *ht) { - return (unsigned long)key % HASH_SIZE; + unsigned long i; + struct rcu_ht_node **prev, *node, *inext; + int cnt = 0; + + for (i = 0; i < ht->size.lookup; i++) { + rcu_read_lock(); + prev = &ht->tbl[i]; + /* + * Cut the head. After that, we own the first element. + */ + node = rcu_xchg_pointer(prev, NULL); + if (!node) { + rcu_read_unlock(); + continue; + } + /* + * We manage a list shared with concurrent writers and readers. + * Note that a concurrent add may or may not be deleted by us, + * depending if it arrives before or after the head is cut. + * "node" points to our first node. Remove first elements + * iteratively. + */ + for (;;) { + inext = NULL; + prev = &node->next; + if (prev) + inext = rcu_xchg_pointer(prev, NULL); + /* + * "node" is the first element of the list we have cut. + * We therefore own it, no concurrent writer may delete + * it. There can only be concurrent lookups. Concurrent + * add can only be done on a bucket head, but we've cut + * it already. inext is also owned by us, because we + * have exchanged it for "NULL". It will therefore be + * safe to use it after a G.P. + */ + rcu_read_unlock(); + if (node->data) + call_rcu(ht->free_fct, node->data); + call_rcu(free, node); + cnt++; + if (likely(!inext)) + break; + rcu_read_lock(); + node = inext; + } + } + return cnt; +} + +/* + * Should only be called when no more concurrent readers nor writers can + * possibly access the table. + */ +int ht_destroy(struct rcu_ht *ht) +{ + int ret; + + ret = ht_delete_all(ht); + free(ht->tbl); + free(ht); + return ret; +} + +/* + * Expects keys <= than pointer size to be encoded in the pointer itself. + */ +uint32_t ht_jhash(void *key, uint32_t length, uint32_t initval) +{ + uint32_t ret; + void *vkey; + + if (length <= sizeof(void *)) + vkey = &key; + else + vkey = key; + ret = jhash(vkey, length, initval); + return ret; }