X-Git-Url: https://git.liburcu.org/?p=urcu.git;a=blobdiff_plain;f=rculfhash.c;h=0c433e54050ea0ba311b91114f6c58b26222629e;hp=720fd0151e20ce70b31b4d5a4012f4ae2d123592;hb=b453eae1adf45ba2377e50c15b438c77c96a35b5;hpb=273399de2e11da5cfea7b412a4bdd904467e9126 diff --git a/rculfhash.c b/rculfhash.c index 720fd01..0c433e5 100644 --- a/rculfhash.c +++ b/rculfhash.c @@ -53,6 +53,15 @@ #define max(a, b) ((a) > (b) ? (a) : (b)) #endif +/* + * The removed flag needs to be updated atomically with the pointer. + * The dummy flag does not require to be updated atomically with the + * pointer, but it is added as a pointer low bit flag to save space. + */ +#define REMOVED_FLAG (1UL << 0) +#define DUMMY_FLAG (1UL << 1) +#define FLAGS_MASK ((1UL << 2) - 1) + struct rcu_table { unsigned long size; /* always a power of 2 */ unsigned long resize_target; @@ -67,6 +76,7 @@ struct rcu_ht { ht_compare_fct compare_fct; unsigned long hash_seed; pthread_mutex_t resize_mutex; /* resize mutex: add/del mutex */ + unsigned int in_progress_resize; void (*ht_call_rcu)(struct rcu_head *head, void (*func)(struct rcu_head *head)); }; @@ -175,21 +185,33 @@ void check_resize(struct rcu_ht *ht, struct rcu_table *t, static struct rcu_ht_node *clear_flag(struct rcu_ht_node *node) { - return (struct rcu_ht_node *) (((unsigned long) node) & ~0x1); + return (struct rcu_ht_node *) (((unsigned long) node) & ~FLAGS_MASK); } static int is_removed(struct rcu_ht_node *node) { - return ((unsigned long) node) & 0x1; + return ((unsigned long) node) & REMOVED_FLAG; } static struct rcu_ht_node *flag_removed(struct rcu_ht_node *node) { - return (struct rcu_ht_node *) (((unsigned long) node) | 0x1); + return (struct rcu_ht_node *) (((unsigned long) node) | REMOVED_FLAG); +} + +static +int is_dummy(struct rcu_ht_node *node) +{ + return ((unsigned long) node) & DUMMY_FLAG; } +static +struct rcu_ht_node *flag_dummy(struct rcu_ht_node *node) +{ + return (struct rcu_ht_node *) (((unsigned long) node) | DUMMY_FLAG); +} + static unsigned long _uatomic_max(unsigned long *ptr, unsigned long v) { @@ -210,39 +232,47 @@ unsigned long _uatomic_max(unsigned long *ptr, unsigned long v) static void _ht_gc_bucket(struct rcu_ht_node *dummy, struct rcu_ht_node *node) { - struct rcu_ht_node *iter_prev, *iter, *next; + struct rcu_ht_node *iter_prev, *iter, *next, *new_next; for (;;) { iter_prev = dummy; /* We can always skip the dummy node initially */ - iter = rcu_dereference(iter_prev->next); - assert(iter_prev->reverse_hash <= node->reverse_hash); - if (unlikely(!iter)) - return; + iter = rcu_dereference(iter_prev->p.next); + assert(iter_prev->p.reverse_hash <= node->p.reverse_hash); for (;;) { - if (clear_flag(iter)->reverse_hash > node->reverse_hash) + if (unlikely(!clear_flag(iter))) + return; + if (clear_flag(iter)->p.reverse_hash > node->p.reverse_hash) return; - next = rcu_dereference(clear_flag(iter)->next); + next = rcu_dereference(clear_flag(iter)->p.next); if (is_removed(next)) break; - if (unlikely(!next)) - return; - iter_prev = iter; + iter_prev = clear_flag(iter); iter = next; } assert(!is_removed(iter)); - (void) uatomic_cmpxchg(&iter_prev->next, iter, clear_flag(next)); + if (is_dummy(iter)) + new_next = flag_dummy(clear_flag(next)); + else + new_next = clear_flag(next); + (void) uatomic_cmpxchg(&iter_prev->p.next, iter, new_next); } } static -int _ht_add(struct rcu_ht *ht, struct rcu_table *t, struct rcu_ht_node *node, - int unique) +struct rcu_ht_node *_ht_add(struct rcu_ht *ht, struct rcu_table *t, + struct rcu_ht_node *node, int unique, int dummy) { - struct rcu_ht_node *iter_prev, *dummy, *iter, *next; - - if (!t->size) - return 0; + struct rcu_ht_node *iter_prev, *iter, *next, *new_node, *new_next, + *dummy_node; + unsigned long hash; + + if (!t->size) { + assert(dummy); + node->p.next = flag_dummy(NULL); + return node; /* Initial first add (head) */ + } + hash = bit_reverse_ulong(node->p.reverse_hash); for (;;) { uint32_t chain_len = 0; @@ -250,20 +280,26 @@ int _ht_add(struct rcu_ht *ht, struct rcu_table *t, struct rcu_ht_node *node, * iter_prev points to the non-removed node prior to the * insert location. */ - iter_prev = rcu_dereference(t->tbl[node->hash & (t->size - 1)]); + iter_prev = rcu_dereference(t->tbl[hash & (t->size - 1)]); /* We can always skip the dummy node initially */ - iter = rcu_dereference(iter_prev->next); - assert(iter_prev->reverse_hash <= node->reverse_hash); + iter = rcu_dereference(iter_prev->p.next); + assert(iter_prev->p.reverse_hash <= node->p.reverse_hash); for (;;) { - if (unlikely(!iter)) + if (unlikely(!clear_flag(iter))) goto insert; - if (clear_flag(iter)->reverse_hash > node->reverse_hash) + if (clear_flag(iter)->p.reverse_hash > node->p.reverse_hash) goto insert; - next = rcu_dereference(clear_flag(iter)->next); + next = rcu_dereference(clear_flag(iter)->p.next); if (is_removed(next)) - goto gc; + goto gc_node; + if (unique + && !is_dummy(next) + && !ht->compare_fct(node->key, node->key_len, + clear_flag(iter)->key, + clear_flag(iter)->key_len)) + return clear_flag(iter); /* Only account for identical reverse hash once */ - if (iter_prev->reverse_hash != clear_flag(iter)->reverse_hash) + if (iter_prev->p.reverse_hash != clear_flag(iter)->p.reverse_hash) check_resize(ht, t, ++chain_len); iter_prev = clear_flag(iter); iter = next; @@ -272,23 +308,33 @@ int _ht_add(struct rcu_ht *ht, struct rcu_table *t, struct rcu_ht_node *node, assert(node != clear_flag(iter)); assert(!is_removed(iter_prev)); assert(iter_prev != node); - node->next = iter; - if (uatomic_cmpxchg(&iter_prev->next, iter, - node) != iter) + if (!dummy) + node->p.next = clear_flag(iter); + else + node->p.next = flag_dummy(clear_flag(iter)); + if (is_dummy(iter)) + new_node = flag_dummy(node); + else + new_node = node; + if (uatomic_cmpxchg(&iter_prev->p.next, iter, + new_node) != iter) continue; /* retry */ else goto gc_end; - gc: - /* Garbage collect logically removed nodes in the bucket */ - dummy = rcu_dereference(t->tbl[node->hash & (t->size - 1)]); - _ht_gc_bucket(dummy, node); + gc_node: + assert(!is_removed(iter)); + if (is_dummy(iter)) + new_next = flag_dummy(clear_flag(next)); + else + new_next = clear_flag(next); + (void) uatomic_cmpxchg(&iter_prev->p.next, iter, new_next); /* retry */ } gc_end: /* Garbage collect logically removed nodes in the bucket */ - dummy = rcu_dereference(t->tbl[node->hash & (t->size - 1)]); - _ht_gc_bucket(dummy, node); - return 0; + dummy_node = rcu_dereference(t->tbl[hash & (t->size - 1)]); + _ht_gc_bucket(dummy_node, node); + return node; } static @@ -296,15 +342,16 @@ int _ht_remove(struct rcu_ht *ht, struct rcu_table *t, struct rcu_ht_node *node) { struct rcu_ht_node *dummy, *next, *old; int flagged = 0; + unsigned long hash; /* logically delete the node */ - old = rcu_dereference(node->next); + old = rcu_dereference(node->p.next); do { next = old; if (is_removed(next)) goto end; - assert(!node->dummy); - old = uatomic_cmpxchg(&node->next, next, + assert(!is_dummy(next)); + old = uatomic_cmpxchg(&node->p.next, next, flag_removed(next)); } while (old != next); @@ -316,7 +363,8 @@ int _ht_remove(struct rcu_ht *ht, struct rcu_table *t, struct rcu_ht_node *node) * the node, and remove it (along with any other logically removed node) * if found. */ - dummy = rcu_dereference(t->tbl[node->hash & (t->size - 1)]); + hash = bit_reverse_ulong(node->p.reverse_hash); + dummy = rcu_dereference(t->tbl[hash & (t->size - 1)]); _ht_gc_bucket(dummy, node); end: /* @@ -324,7 +372,7 @@ end: * removed the node from the hash. */ if (flagged) { - assert(is_removed(rcu_dereference(node->next))); + assert(is_removed(rcu_dereference(node->p.next))); return 0; } else return -ENOENT; @@ -341,11 +389,9 @@ void init_table(struct rcu_ht *ht, struct rcu_table *t, /* Update table size when power of two */ if (i != 0 && !(i & (i - 1))) t->size = i; - t->tbl[i] = calloc(1, sizeof(struct rcu_ht_node)); - t->tbl[i]->dummy = 1; - t->tbl[i]->hash = i; - t->tbl[i]->reverse_hash = bit_reverse_ulong(i); - (void) _ht_add(ht, t, t->tbl[i], 0); + t->tbl[i] = calloc(1, sizeof(struct _rcu_ht_node)); + t->tbl[i]->p.reverse_hash = bit_reverse_ulong(i); + (void) _ht_add(ht, t, t->tbl[i], 0, 1); } t->resize_target = t->size = end; t->resize_initiated = 0; @@ -365,6 +411,7 @@ struct rcu_ht *ht_new(ht_hash_fct hash_fct, ht->compare_fct = compare_fct; ht->hash_seed = hash_seed; ht->ht_call_rcu = ht_call_rcu; + ht->in_progress_resize = 0; /* this mutex should not nest in read-side C.S. */ pthread_mutex_init(&ht->resize_mutex, NULL); ht->t = calloc(1, sizeof(struct rcu_table) @@ -379,7 +426,7 @@ struct rcu_ht *ht_new(ht_hash_fct hash_fct, struct rcu_ht_node *ht_lookup(struct rcu_ht *ht, void *key, size_t key_len) { struct rcu_table *t; - struct rcu_ht_node *node; + struct rcu_ht_node *node, *next; unsigned long hash, reverse_hash; hash = ht->hash_fct(key, key_len, ht->hash_seed); @@ -390,41 +437,44 @@ struct rcu_ht_node *ht_lookup(struct rcu_ht *ht, void *key, size_t key_len) for (;;) { if (unlikely(!node)) break; - if (unlikely(node->reverse_hash > reverse_hash)) { + if (unlikely(node->p.reverse_hash > reverse_hash)) { node = NULL; break; } - if (!ht->compare_fct(node->key, node->key_len, key, key_len)) { - if (likely(!is_removed(rcu_dereference(node->next))) - && likely(!node->dummy)) + next = rcu_dereference(node->p.next); + if (likely(!is_removed(next)) + && !is_dummy(next) + && likely(!ht->compare_fct(node->key, node->key_len, key, key_len))) { break; } - node = clear_flag(rcu_dereference(node->next)); + node = clear_flag(next); } - assert(!node || !node->dummy); + assert(!node || !is_dummy(rcu_dereference(node->p.next))); return node; } void ht_add(struct rcu_ht *ht, struct rcu_ht_node *node) { struct rcu_table *t; + unsigned long hash; - node->hash = ht->hash_fct(node->key, node->key_len, ht->hash_seed); - node->reverse_hash = bit_reverse_ulong((unsigned long) node->hash); + hash = ht->hash_fct(node->key, node->key_len, ht->hash_seed); + node->p.reverse_hash = bit_reverse_ulong((unsigned long) hash); t = rcu_dereference(ht->t); - (void) _ht_add(ht, t, node, 0); + (void) _ht_add(ht, t, node, 0, 0); } -int ht_add_unique(struct rcu_ht *ht, struct rcu_ht_node *node) +struct rcu_ht_node *ht_add_unique(struct rcu_ht *ht, struct rcu_ht_node *node) { struct rcu_table *t; + unsigned long hash; - node->hash = ht->hash_fct(node->key, node->key_len, ht->hash_seed); - node->reverse_hash = bit_reverse_ulong((unsigned long) node->hash); + hash = ht->hash_fct(node->key, node->key_len, ht->hash_seed); + node->p.reverse_hash = bit_reverse_ulong((unsigned long) hash); t = rcu_dereference(ht->t); - return _ht_add(ht, t, node, 1); + return _ht_add(ht, t, node, 1, 0); } int ht_remove(struct rcu_ht *ht, struct rcu_ht_node *node) @@ -446,14 +496,14 @@ int ht_delete_dummy(struct rcu_ht *ht) /* Check that the table is empty */ node = t->tbl[0]; do { - if (!node->dummy) + node = clear_flag(node)->p.next; + if (!is_dummy(node)) return -EPERM; - node = node->next; assert(!is_removed(node)); - } while (node); + } while (clear_flag(node)); /* Internal sanity check: all nodes left should be dummy */ for (i = 0; i < t->size; i++) { - assert(t->tbl[i]->dummy); + assert(is_dummy(t->tbl[i]->p.next)); free(t->tbl[i]); } return 0; @@ -467,6 +517,9 @@ int ht_destroy(struct rcu_ht *ht) { int ret; + /* Wait for in-flight resize operations to complete */ + while (uatomic_read(&ht->in_progress_resize)) + poll(NULL, 0, 100); /* wait for 100ms */ ret = ht_delete_dummy(ht); if (ret) return ret; @@ -489,11 +542,11 @@ void ht_count_nodes(struct rcu_ht *ht, /* Check that the table is empty */ node = rcu_dereference(t->tbl[0]); do { - next = rcu_dereference(node->next); + next = rcu_dereference(node->p.next); if (is_removed(next)) { - assert(!node->dummy); + assert(!is_dummy(next)); (*removed)++; - } else if (!node->dummy) + } else if (!is_dummy(next)) (*count)++; node = clear_flag(next); } while (node); @@ -566,6 +619,8 @@ void do_resize_cb(struct rcu_head *head) _do_ht_resize(ht); pthread_mutex_unlock(&ht->resize_mutex); free(work); + cmm_smp_mb(); /* finish resize before decrement */ + uatomic_dec(&ht->in_progress_resize); } static @@ -576,6 +631,8 @@ void ht_resize_lazy(struct rcu_ht *ht, struct rcu_table *t, int growth) target_size = resize_target_update(t, growth); if (!CMM_LOAD_SHARED(t->resize_initiated) && t->size < target_size) { + uatomic_inc(&ht->in_progress_resize); + cmm_smp_mb(); /* increment resize count before calling it */ work = malloc(sizeof(*work)); work->ht = ht; ht->ht_call_rcu(&work->head, do_resize_cb);