Commit ca26893f authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller

rhashtable: Add rhlist interface

The insecure_elasticity setting is an ugly wart brought out by
users who need to insert duplicate objects (that is, distinct
objects with identical keys) into the same table.

In fact, those users have a much bigger problem.  Once those
duplicate objects are inserted, they don't have an interface to
find them (unless you count the walker interface which walks
over the entire table).

Some users have resorted to doing a manual walk over the hash
table which is of course broken because they don't handle the
potential existence of multiple hash tables.  The result is that
they will break sporadically when they encounter a hash table
resize/rehash.

This patch provides a way out for those users, at the expense
of an extra pointer per object.  Essentially each object is now
a list of objects carrying the same key.  The hash table will
only see the lists so nothing changes as far as rhashtable is
concerned.

To use this new interface, you need to insert a struct rhlist_head
into your objects instead of struct rhash_head.  While the hash
table is unchanged, for type-safety you'll need to use struct
rhltable instead of struct rhashtable.  All the existing interfaces
have been duplicated for rhlist, including the hash table walker.

One missing feature is nulls marking because AFAIK the only potential
user of it does not need duplicate objects.  Should anyone need
this it shouldn't be too hard to add.
Signed-off-by: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Acked-by: default avatarThomas Graf <tgraf@suug.ch>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent fd07160b
This diff is collapsed.
...@@ -378,22 +378,8 @@ static void rht_deferred_worker(struct work_struct *work) ...@@ -378,22 +378,8 @@ static void rht_deferred_worker(struct work_struct *work)
schedule_work(&ht->run_work); schedule_work(&ht->run_work);
} }
static bool rhashtable_check_elasticity(struct rhashtable *ht, static int rhashtable_insert_rehash(struct rhashtable *ht,
struct bucket_table *tbl, struct bucket_table *tbl)
unsigned int hash)
{
unsigned int elasticity = ht->elasticity;
struct rhash_head *head;
rht_for_each(head, tbl, hash)
if (!--elasticity)
return true;
return false;
}
int rhashtable_insert_rehash(struct rhashtable *ht,
struct bucket_table *tbl)
{ {
struct bucket_table *old_tbl; struct bucket_table *old_tbl;
struct bucket_table *new_tbl; struct bucket_table *new_tbl;
...@@ -439,57 +425,165 @@ fail: ...@@ -439,57 +425,165 @@ fail:
return err; return err;
} }
EXPORT_SYMBOL_GPL(rhashtable_insert_rehash);
struct bucket_table *rhashtable_insert_slow(struct rhashtable *ht, static void *rhashtable_lookup_one(struct rhashtable *ht,
const void *key, struct bucket_table *tbl, unsigned int hash,
struct rhash_head *obj, const void *key, struct rhash_head *obj)
struct bucket_table *tbl,
void **data)
{ {
struct rhashtable_compare_arg arg = {
.ht = ht,
.key = key,
};
struct rhash_head __rcu **pprev;
struct rhash_head *head; struct rhash_head *head;
unsigned int hash; int elasticity;
int err;
tbl = rhashtable_last_table(ht, tbl); elasticity = ht->elasticity;
hash = head_hashfn(ht, tbl, obj); pprev = &tbl->buckets[hash];
spin_lock_nested(rht_bucket_lock(tbl, hash), SINGLE_DEPTH_NESTING); rht_for_each(head, tbl, hash) {
struct rhlist_head *list;
err = -EEXIST; struct rhlist_head *plist;
if (key) {
*data = rhashtable_lookup_fast(ht, key, ht->p); elasticity--;
if (*data) if (!key ||
goto exit; (ht->p.obj_cmpfn ?
ht->p.obj_cmpfn(&arg, rht_obj(ht, head)) :
rhashtable_compare(&arg, rht_obj(ht, head))))
continue;
if (!ht->rhlist)
return rht_obj(ht, head);
list = container_of(obj, struct rhlist_head, rhead);
plist = container_of(head, struct rhlist_head, rhead);
RCU_INIT_POINTER(list->next, plist);
head = rht_dereference_bucket(head->next, tbl, hash);
RCU_INIT_POINTER(list->rhead.next, head);
rcu_assign_pointer(*pprev, obj);
return NULL;
} }
err = -E2BIG; if (elasticity <= 0)
if (unlikely(rht_grow_above_max(ht, tbl))) return ERR_PTR(-EAGAIN);
goto exit;
return ERR_PTR(-ENOENT);
}
static struct bucket_table *rhashtable_insert_one(struct rhashtable *ht,
struct bucket_table *tbl,
unsigned int hash,
struct rhash_head *obj,
void *data)
{
struct bucket_table *new_tbl;
struct rhash_head *head;
if (!IS_ERR_OR_NULL(data))
return ERR_PTR(-EEXIST);
err = -EAGAIN; if (PTR_ERR(data) != -EAGAIN && PTR_ERR(data) != -ENOENT)
if (rhashtable_check_elasticity(ht, tbl, hash) || return ERR_CAST(data);
rht_grow_above_100(ht, tbl))
goto exit;
err = 0; new_tbl = rcu_dereference(tbl->future_tbl);
if (new_tbl)
return new_tbl;
if (PTR_ERR(data) != -ENOENT)
return ERR_CAST(data);
if (unlikely(rht_grow_above_max(ht, tbl)))
return ERR_PTR(-E2BIG);
if (unlikely(rht_grow_above_100(ht, tbl)))
return ERR_PTR(-EAGAIN);
head = rht_dereference_bucket(tbl->buckets[hash], tbl, hash); head = rht_dereference_bucket(tbl->buckets[hash], tbl, hash);
RCU_INIT_POINTER(obj->next, head); RCU_INIT_POINTER(obj->next, head);
if (ht->rhlist) {
struct rhlist_head *list;
list = container_of(obj, struct rhlist_head, rhead);
RCU_INIT_POINTER(list->next, NULL);
}
rcu_assign_pointer(tbl->buckets[hash], obj); rcu_assign_pointer(tbl->buckets[hash], obj);
atomic_inc(&ht->nelems); atomic_inc(&ht->nelems);
if (rht_grow_above_75(ht, tbl))
schedule_work(&ht->run_work);
exit: return NULL;
spin_unlock(rht_bucket_lock(tbl, hash)); }
if (err == 0) static void *rhashtable_try_insert(struct rhashtable *ht, const void *key,
return NULL; struct rhash_head *obj)
else if (err == -EAGAIN) {
return tbl; struct bucket_table *new_tbl;
else struct bucket_table *tbl;
return ERR_PTR(err); unsigned int hash;
spinlock_t *lock;
void *data;
tbl = rcu_dereference(ht->tbl);
/* All insertions must grab the oldest table containing
* the hashed bucket that is yet to be rehashed.
*/
for (;;) {
hash = rht_head_hashfn(ht, tbl, obj, ht->p);
lock = rht_bucket_lock(tbl, hash);
spin_lock_bh(lock);
if (tbl->rehash <= hash)
break;
spin_unlock_bh(lock);
tbl = rcu_dereference(tbl->future_tbl);
}
data = rhashtable_lookup_one(ht, tbl, hash, key, obj);
new_tbl = rhashtable_insert_one(ht, tbl, hash, obj, data);
if (PTR_ERR(new_tbl) != -EEXIST)
data = ERR_CAST(new_tbl);
while (!IS_ERR_OR_NULL(new_tbl)) {
tbl = new_tbl;
hash = rht_head_hashfn(ht, tbl, obj, ht->p);
spin_lock_nested(rht_bucket_lock(tbl, hash),
SINGLE_DEPTH_NESTING);
data = rhashtable_lookup_one(ht, tbl, hash, key, obj);
new_tbl = rhashtable_insert_one(ht, tbl, hash, obj, data);
if (PTR_ERR(new_tbl) != -EEXIST)
data = ERR_CAST(new_tbl);
spin_unlock(rht_bucket_lock(tbl, hash));
}
spin_unlock_bh(lock);
if (PTR_ERR(data) == -EAGAIN)
data = ERR_PTR(rhashtable_insert_rehash(ht, tbl) ?:
-EAGAIN);
return data;
}
void *rhashtable_insert_slow(struct rhashtable *ht, const void *key,
struct rhash_head *obj)
{
void *data;
do {
rcu_read_lock();
data = rhashtable_try_insert(ht, key, obj);
rcu_read_unlock();
} while (PTR_ERR(data) == -EAGAIN);
return data;
} }
EXPORT_SYMBOL_GPL(rhashtable_insert_slow); EXPORT_SYMBOL_GPL(rhashtable_insert_slow);
...@@ -593,11 +687,16 @@ EXPORT_SYMBOL_GPL(rhashtable_walk_start); ...@@ -593,11 +687,16 @@ EXPORT_SYMBOL_GPL(rhashtable_walk_start);
void *rhashtable_walk_next(struct rhashtable_iter *iter) void *rhashtable_walk_next(struct rhashtable_iter *iter)
{ {
struct bucket_table *tbl = iter->walker.tbl; struct bucket_table *tbl = iter->walker.tbl;
struct rhlist_head *list = iter->list;
struct rhashtable *ht = iter->ht; struct rhashtable *ht = iter->ht;
struct rhash_head *p = iter->p; struct rhash_head *p = iter->p;
bool rhlist = ht->rhlist;
if (p) { if (p) {
p = rht_dereference_bucket_rcu(p->next, tbl, iter->slot); if (!rhlist || !(list = rcu_dereference(list->next))) {
p = rcu_dereference(p->next);
list = container_of(p, struct rhlist_head, rhead);
}
goto next; goto next;
} }
...@@ -605,6 +704,18 @@ void *rhashtable_walk_next(struct rhashtable_iter *iter) ...@@ -605,6 +704,18 @@ void *rhashtable_walk_next(struct rhashtable_iter *iter)
int skip = iter->skip; int skip = iter->skip;
rht_for_each_rcu(p, tbl, iter->slot) { rht_for_each_rcu(p, tbl, iter->slot) {
if (rhlist) {
list = container_of(p, struct rhlist_head,
rhead);
do {
if (!skip)
goto next;
skip--;
list = rcu_dereference(list->next);
} while (list);
continue;
}
if (!skip) if (!skip)
break; break;
skip--; skip--;
...@@ -614,7 +725,8 @@ next: ...@@ -614,7 +725,8 @@ next:
if (!rht_is_a_nulls(p)) { if (!rht_is_a_nulls(p)) {
iter->skip++; iter->skip++;
iter->p = p; iter->p = p;
return rht_obj(ht, p); iter->list = list;
return rht_obj(ht, rhlist ? &list->rhead : p);
} }
iter->skip = 0; iter->skip = 0;
...@@ -802,6 +914,48 @@ int rhashtable_init(struct rhashtable *ht, ...@@ -802,6 +914,48 @@ int rhashtable_init(struct rhashtable *ht,
} }
EXPORT_SYMBOL_GPL(rhashtable_init); EXPORT_SYMBOL_GPL(rhashtable_init);
/**
* rhltable_init - initialize a new hash list table
* @hlt: hash list table to be initialized
* @params: configuration parameters
*
* Initializes a new hash list table.
*
* See documentation for rhashtable_init.
*/
int rhltable_init(struct rhltable *hlt, const struct rhashtable_params *params)
{
int err;
/* No rhlist NULLs marking for now. */
if (params->nulls_base)
return -EINVAL;
err = rhashtable_init(&hlt->ht, params);
hlt->ht.rhlist = true;
return err;
}
EXPORT_SYMBOL_GPL(rhltable_init);
static void rhashtable_free_one(struct rhashtable *ht, struct rhash_head *obj,
void (*free_fn)(void *ptr, void *arg),
void *arg)
{
struct rhlist_head *list;
if (!ht->rhlist) {
free_fn(rht_obj(ht, obj), arg);
return;
}
list = container_of(obj, struct rhlist_head, rhead);
do {
obj = &list->rhead;
list = rht_dereference(list->next, ht);
free_fn(rht_obj(ht, obj), arg);
} while (list);
}
/** /**
* rhashtable_free_and_destroy - free elements and destroy hash table * rhashtable_free_and_destroy - free elements and destroy hash table
* @ht: the hash table to destroy * @ht: the hash table to destroy
...@@ -839,7 +993,7 @@ void rhashtable_free_and_destroy(struct rhashtable *ht, ...@@ -839,7 +993,7 @@ void rhashtable_free_and_destroy(struct rhashtable *ht,
pos = next, pos = next,
next = !rht_is_a_nulls(pos) ? next = !rht_is_a_nulls(pos) ?
rht_dereference(pos->next, ht) : NULL) rht_dereference(pos->next, ht) : NULL)
free_fn(rht_obj(ht, pos), arg); rhashtable_free_one(ht, pos, free_fn, arg);
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment