]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
Slabbed hash table. Drops contention on hash table main lock.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Fri, 23 Mar 2007 15:17:11 +0000 (15:17 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Fri, 23 Mar 2007 15:17:11 +0000 (15:17 +0000)
git-svn-id: file:///svn/unbound/trunk@194 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
testcode/checklocks.c
testcode/checklocks.h
testcode/unitlruhash.c
testcode/unitmain.c
testcode/unitmain.h
testcode/unitslabhash.c [new file with mode: 0644]
util/storage/lruhash.c
util/storage/lruhash.h
util/storage/slabhash.c [new file with mode: 0644]
util/storage/slabhash.h [new file with mode: 0644]

index 0031d40a00d13301ffef5039b4482b766d02a092..f6727a0c1f6795e53074849949cf71b5e1762f5d 100644 (file)
@@ -3,6 +3,7 @@
        - covered up memory leak of the entry locks.
        - answers from the cache correctly. Copies flags correctly.
        - sanity check for incoming query replies.
+       - slabbed hash table. Much nicer contention, need dual cpu to see.
 
 22 March 2007: Wouter
        - AIX configure check.
index dda3be2fa893d0e4ec4aec1e0a73768f14afa194..445dbaa797da1de3d2b780a2d7f4d4d64c9398e6 100644 (file)
  */
 #ifdef USE_THREAD_DEBUG
 
+/** How long to wait before lock attempt is a failure. */
+#define CHECK_LOCK_TIMEOUT 15 /* seconds */
+/** How long to wait before join attempt is a failure. */
+#define CHECK_JOIN_TIMEOUT 120 /* seconds */
+
 /** if key has been created */
 static int key_created = 0;
 /** we hide the thread debug info with this key. */
@@ -326,10 +331,11 @@ checklock_destroy(enum check_lock_type type, struct checked_lock** lock,
        /* contention, look at fraction in trouble. */
        if(e->history_count > 1 &&
           1000*e->contention_count/e->history_count > contention_interest) {
-               log_info("lock created %s %s %d has contention %u of %u",
+               log_info("lock created %s %s %d has contention %u of %u (%d%%)",
                        e->create_func, e->create_file, e->create_line,
                        (unsigned int)e->contention_count, 
-                       (unsigned int)e->history_count);
+                       (unsigned int)e->history_count,
+                       100*e->contention_count/e->history_count);
        }
 
        /* delete it */
index 72ad9ef04f2a6c9b79a3ef64976ffae057f0911f..500bb9fc3924e467d1a6253aac58d3c70f792dc7 100644 (file)
 /******************* THREAD DEBUG ************************/
 #include <pthread.h>
 
-/** How long to wait before lock attempt is a failure. */
-#define CHECK_LOCK_TIMEOUT 5 /* seconds */
-/** How long to wait before join attempt is a failure. */
-#define CHECK_JOIN_TIMEOUT 120 /* seconds */
 /** How many threads to allocate for */
 #define THRDEBUG_MAX_THREADS 32 /* threads */
 /** do we check locking order */
index a6af777586cf52c7d1046ce433c405add809c041..72138f0e99c3a663aecdb7d9e6e48571fcb3197a 100644 (file)
@@ -72,6 +72,7 @@ static hashvalue_t myhash(int id) {return (hashvalue_t)id & 0x0f;}
 /** allocate new key, fill in hash. */
 static struct testkey* newkey(int id) {
        struct testkey* k = (struct testkey*)calloc(1, sizeof(struct testkey));
+       if(!k) fatal_exit("out of memory");
        k->id = id;
        k->entry.hash = myhash(id);
        k->entry.key = k;
@@ -82,6 +83,7 @@ static struct testkey* newkey(int id) {
 static struct testdata* newdata(int val) {
        struct testdata* d = (struct testdata*)calloc(1, 
                sizeof(struct testdata));
+       if(!d) fatal_exit("out of memory");
        d->data = val;
        return d;
 }
@@ -102,6 +104,7 @@ test_bin_find_entry(struct lruhash* table)
        struct testkey* k4 = newkey(12 + 1024*2);
        hashvalue_t h = myhash(12);
        struct lruhash_bin bin;
+       memset(&bin, 0, sizeof(bin));
        bin_init(&bin, 1);
 
        /* remove from empty list */
@@ -488,7 +491,9 @@ void lruhash_test()
 {
        /* start very very small array, so it can do lots of table_grow() */
        /* also small in size so that reclaim has to be done quickly. */
-       struct lruhash* table = lruhash_create(2, 4096, 
+       struct lruhash* table ;
+       printf("lruhash test\n");
+       table = lruhash_create(2, 4096, 
                test_sizefunc, test_compfunc, test_delkey, test_deldata, NULL);
        test_bin_find_entry(table);
        test_lru(table);
index c515377161ae5cdd13c687632b43c0b3f4d56aa2..4790235075ba0183c9b9adae37e7faa7d9e7be80 100644 (file)
@@ -111,7 +111,7 @@ dname_to_buf(ldns_buffer* b, const char* str)
        if(status != LDNS_STATUS_OK)
                fatal_exit("%s ldns: %s", __func__, 
                        ldns_get_errorstr_by_id(status));
-       ldns_rdf_free(rdf);
+       ldns_rdf_deep_free(rdf);
        ldns_buffer_flip(b);
        return b;
 }
@@ -150,6 +150,7 @@ main(int argc, char* argv[])
        alloc_test();
        msgreply_test();
        lruhash_test();
+       slabhash_test();
        checklock_stop();
        printf("%d tests succeeded\n", testcount);
        return 0;
index ae0b370575ff2c5f448f51cc714b8900d5b31729..220d757de9431a2b91fc2ae147ebd7ca36eda38c 100644 (file)
@@ -49,5 +49,7 @@ extern int testcount;
 
 /** unit test lruhashtable implementation */
 void lruhash_test();
+/** unit test slabhashtable implementation */
+void slabhash_test();
 
 #endif /* TESTCODE_UNITMAIN_H */
diff --git a/testcode/unitslabhash.c b/testcode/unitslabhash.c
new file mode 100644 (file)
index 0000000..e6bf7bb
--- /dev/null
@@ -0,0 +1,415 @@
+/*
+ * testcode/unitslabhash.c - unit test for slabhash table.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+/**
+ * \file
+ * Tests the locking LRU keeping hash table implementation.
+ */
+
+#include "config.h"
+#include "testcode/unitmain.h"
+#include "util/log.h"
+#include "util/storage/slabhash.h"
+
+/* --- test representation --- */
+/** structure contains test key */
+struct slabtestkey {
+       /** the key id */
+       int id;
+       /** the entry */
+       struct lruhash_entry entry;
+};
+/** structure contains test data */
+struct slabtestdata {
+       /** data value */
+       int data;
+};
+
+/** sizefunc for lruhash */
+static size_t test_sizefunc(void*, void*);
+/** comparefunc for lruhash */
+static int test_compfunc(void*, void*);
+/** delkey for lruhash */
+static void test_delkey(void*, void*);
+/** deldata for lruhash */
+static void test_deldata(void*, void*);
+/* --- end test representation --- */
+
+/** hash func, very bad to improve collisions, both high and low bits. */
+static hashvalue_t myhash(int id) {
+       hashvalue_t h = (hashvalue_t)id & 0x0f;
+       h |= (h << 28);
+       return h;
+}
+
+/** allocate new key, fill in hash. */
+static struct slabtestkey* newkey(int id) {
+       struct slabtestkey* k = (struct slabtestkey*)calloc(1, sizeof(struct slabtestkey));
+       if(!k) fatal_exit("out of memory");
+       k->id = id;
+       k->entry.hash = myhash(id);
+       k->entry.key = k;
+       lock_rw_init(&k->entry.lock);
+       return k;
+}
+/** new data el */
+static struct slabtestdata* newdata(int val) {
+       struct slabtestdata* d = (struct slabtestdata*)calloc(1, 
+               sizeof(struct slabtestdata));
+       if(!d) fatal_exit("out of memory");
+       d->data = val;
+       return d;
+}
+/** delete key */
+static void delkey(struct slabtestkey* k) {    
+       lock_rw_destroy(&k->entry.lock); free(k);}
+/** delete data */
+static void deldata(struct slabtestdata* d) {free(d);}
+
+/** test hashtable using short sequence */
+static void
+test_short_table(struct slabhash* table) 
+{
+       struct slabtestkey* k = newkey(12);
+       struct slabtestkey* k2 = newkey(14);
+       struct slabtestdata* d = newdata(128);
+       struct slabtestdata* d2 = newdata(129);
+       
+       k->entry.data = d;
+       k2->entry.data = d2;
+
+       slabhash_insert(table, myhash(12), &k->entry, d);
+       slabhash_insert(table, myhash(14), &k2->entry, d2);
+       
+       unit_assert( slabhash_lookup(table, myhash(12), k, 0) == &k->entry);
+       lock_rw_unlock( &k->entry.lock );
+       unit_assert( slabhash_lookup(table, myhash(14), k2, 0) == &k2->entry);
+       lock_rw_unlock( &k2->entry.lock );
+       slabhash_remove(table, myhash(12), k);
+       slabhash_remove(table, myhash(14), k2);
+}
+
+/** number of hash test max */
+#define HASHTESTMAX 100
+
+/** test adding a random element */
+static void
+testadd(struct slabhash* table, struct slabtestdata* ref[])
+{
+       int numtoadd = random() % HASHTESTMAX;
+       struct slabtestdata* data = newdata(numtoadd);
+       struct slabtestkey* key = newkey(numtoadd);
+       key->entry.data = data;
+       slabhash_insert(table, myhash(numtoadd), &key->entry, data);
+       ref[numtoadd] = data;
+}
+
+/** test adding a random element */
+static void
+testremove(struct slabhash* table, struct slabtestdata* ref[])
+{
+       int num = random() % HASHTESTMAX;
+       struct slabtestkey* key = newkey(num);
+       slabhash_remove(table, myhash(num), key);
+       ref[num] = NULL;
+       delkey(key);
+}
+
+/** test adding a random element */
+static void
+testlookup(struct slabhash* table, struct slabtestdata* ref[])
+{
+       int num = random() % HASHTESTMAX;
+       struct slabtestkey* key = newkey(num);
+       struct lruhash_entry* en = slabhash_lookup(table, myhash(num), key, 0);
+       struct slabtestdata* data = en? (struct slabtestdata*)en->data : NULL;
+       if(en) {
+               unit_assert(en->key);
+               unit_assert(en->data);
+       }
+       if(0) log_info("lookup %d got %d, expect %d", num, en? data->data :-1,
+               ref[num]? ref[num]->data : -1);
+       unit_assert( data == ref[num] );
+       if(en) lock_rw_unlock(&en->lock);
+       delkey(key);
+}
+
+/** check integrity of hash table */
+static void
+check_lru_table(struct lruhash* table)
+{
+       struct lruhash_entry* p;
+       size_t c = 0;
+       lock_quick_lock(&table->lock);
+       unit_assert( table->num <= table->size);
+       unit_assert( table->size_mask == (int)table->size-1 );
+       unit_assert( (table->lru_start && table->lru_end) ||
+               (!table->lru_start && !table->lru_end) );
+       unit_assert( table->space_used <= table->space_max );
+       /* check lru list integrity */
+       if(table->lru_start)
+               unit_assert(table->lru_start->lru_prev == NULL);
+       if(table->lru_end)
+               unit_assert(table->lru_end->lru_next == NULL);
+       p = table->lru_start;
+       while(p) {
+               if(p->lru_prev) {
+                       unit_assert(p->lru_prev->lru_next == p);
+               }
+               if(p->lru_next) {
+                       unit_assert(p->lru_next->lru_prev == p);
+               }
+               c++;
+               p = p->lru_next;
+       }
+       unit_assert(c == table->num);
+
+       /* this assertion is specific to the unit test */
+       unit_assert( table->space_used == 
+               table->num * test_sizefunc(NULL, NULL) );
+       lock_quick_unlock(&table->lock);
+}
+
+/** check integrity of hash table */
+static void
+check_table(struct slabhash* table)
+{
+       size_t i;
+       for(i=0; i<table->size; i++)
+               check_lru_table(table->array[i]);
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testadd_unlim(struct slabhash* table, struct slabtestdata** ref)
+{
+       int numtoadd = random() % (HASHTESTMAX * 10);
+       struct slabtestdata* data = newdata(numtoadd);
+       struct slabtestkey* key = newkey(numtoadd);
+       key->entry.data = data;
+       slabhash_insert(table, myhash(numtoadd), &key->entry, data);
+       if(ref)
+               ref[numtoadd] = data;
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testremove_unlim(struct slabhash* table, struct slabtestdata** ref)
+{
+       int num = random() % (HASHTESTMAX*10);
+       struct slabtestkey* key = newkey(num);
+       slabhash_remove(table, myhash(num), key);
+       if(ref)
+               ref[num] = NULL;
+       delkey(key);
+}
+
+/** test adding a random element (unlimited range) */
+static void
+testlookup_unlim(struct slabhash* table, struct slabtestdata** ref)
+{
+       int num = random() % (HASHTESTMAX*10);
+       struct slabtestkey* key = newkey(num);
+       struct lruhash_entry* en = slabhash_lookup(table, myhash(num), key, 0);
+       struct slabtestdata* data = en? (struct slabtestdata*)en->data : NULL;
+       if(en) {
+               unit_assert(en->key);
+               unit_assert(en->data);
+       }
+       if(0 && ref) log_info("lookup unlim %d got %d, expect %d", num, en ? 
+               data->data :-1, ref[num] ? ref[num]->data : -1);
+       if(data && ref) {
+               /* its okay for !data, it fell off the lru */
+               unit_assert( data == ref[num] );
+       }
+       if(en) lock_rw_unlock(&en->lock);
+       delkey(key);
+}
+
+/** test with long sequence of adds, removes and updates, and lookups */
+static void
+test_long_table(struct slabhash* table) 
+{
+       /* assuming it all fits in the hastable, this check will work */
+       struct slabtestdata* ref[HASHTESTMAX * 100];
+       size_t i;
+       memset(ref, 0, sizeof(ref));
+       /* test assumption */
+       if(0) slabhash_status(table, "unit test", 1);
+       srandom(48);
+       for(i=0; i<1000; i++) {
+               /* what to do? */
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd(table, ref);
+                               break;
+                       case 1:
+                               testremove(table, ref);
+                               break;
+                       case 2:
+                               testlookup(table, ref);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) slabhash_status(table, "unit test", 1);
+               check_table(table);
+       }
+
+       /* test more, but 'ref' assumption does not hold anymore */
+       for(i=0; i<1000; i++) {
+               /* what to do? */
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd_unlim(table, ref);
+                               break;
+                       case 1:
+                               testremove_unlim(table, ref);
+                               break;
+                       case 2:
+                               testlookup_unlim(table, ref);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) slabhash_status(table, "unlim", 1);
+               check_table(table);
+       }
+}
+
+/** structure to threaded test the lru hash table */
+struct slab_test_thr {
+       /** thread num, first entry. */
+       int num;
+       /** id */
+       ub_thread_t id;
+       /** hash table */
+       struct slabhash* table;
+};
+
+/** main routine for threaded hash table test */
+static void*
+test_thr_main(void* arg) 
+{
+       struct slab_test_thr* t = (struct slab_test_thr*)arg;
+       int i;
+       log_thread_set(&t->num);
+       for(i=0; i<1000; i++) {
+               switch(random() % 4) {
+                       case 0:
+                       case 3:
+                               testadd_unlim(t->table, NULL);
+                               break;
+                       case 1:
+                               testremove_unlim(t->table, NULL);
+                               break;
+                       case 2:
+                               testlookup_unlim(t->table, NULL);
+                               break;
+                       default:
+                               unit_assert(0);
+               }
+               if(0) slabhash_status(t->table, "hashtest", 1);
+               if(i % 100 == 0) /* because of locking, not all the time */
+                       check_table(t->table);
+       }
+       check_table(t->table);
+       return NULL;
+}
+
+/** test hash table access by multiple threads. */
+static void
+test_threaded_table(struct slabhash* table)
+{
+       int numth = 10;
+       struct slab_test_thr t[100];
+       int i;
+
+       for(i=1; i<numth; i++) {
+               t[i].num = i;
+               t[i].table = table;
+               ub_thread_create(&t[i].id, test_thr_main, &t[i]);
+       }
+
+       for(i=1; i<numth; i++) {
+               ub_thread_join(t[i].id);
+       }
+       if(0) slabhash_status(table, "hashtest", 1);
+}
+
+void slabhash_test()
+{
+       /* start very very small array, so it can do lots of table_grow() */
+       /* also small in size so that reclaim has to be done quickly. */
+       struct slabhash* table;
+       printf("slabhash test\n");
+       table = slabhash_create(4, 2, 4096, 
+               test_sizefunc, test_compfunc, test_delkey, test_deldata, NULL);
+       test_short_table(table);
+       test_long_table(table);
+       slabhash_delete(table);
+       table = slabhash_create(4, 2, 4096, 
+               test_sizefunc, test_compfunc, test_delkey, test_deldata, NULL);
+       test_threaded_table(table);
+       slabhash_delete(table);
+}
+
+static size_t test_sizefunc(void* ATTR_UNUSED(key), void* ATTR_UNUSED(data))
+{
+       return sizeof(struct slabtestkey) + sizeof(struct slabtestdata);
+}
+
+static int test_compfunc(void* key1, void* key2)
+{
+       struct slabtestkey* k1 = (struct slabtestkey*)key1;
+       struct slabtestkey* k2 = (struct slabtestkey*)key2;
+       if(k1->id == k2->id)
+               return 0;
+       if(k1->id > k2->id)
+               return 1;
+       return -1;
+}
+
+static void test_delkey(void* key, void* ATTR_UNUSED(arg))
+{
+       delkey((struct slabtestkey*)key);
+}
+
+static void test_deldata(void* data, void* ATTR_UNUSED(arg))
+{
+       deldata((struct slabtestdata*)data);
+}
index ba00fc4944f00b9d61ae8f5d82710510e1fae51e..3db99d82bb3ee89d2f1074688e23b465ba23bb3a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * util/storage/lrulash.c - hashtable, hash function, LRU keeping.
+ * util/storage/lruhash.c - hashtable, hash function, LRU keeping.
  *
  * Copyright (c) 2007, NLnet Labs. All rights reserved.
  *
@@ -329,9 +329,10 @@ lruhash_insert(struct lruhash* table, hashvalue_t hash,
        /* finish reclaim if any (outside of critical region) */
        while(reclaimlist) {
                struct lruhash_entry* n = reclaimlist->overflow_next;
+               void* d = reclaimlist->data;
                lock_rw_unlock(&reclaimlist->lock);
                (*table->delkeyfunc)(reclaimlist->key, table->cb_arg);
-               (*table->deldatafunc)(reclaimlist->data, table->cb_arg);
+               (*table->deldatafunc)(d, table->cb_arg);
                reclaimlist = n;
        }
 }
@@ -363,6 +364,7 @@ lruhash_remove(struct lruhash* table, hashvalue_t hash, void* key)
 {
        struct lruhash_entry* entry;
        struct lruhash_bin* bin;
+       void *d;
 
        lock_quick_lock(&table->lock);
        bin = &table->array[hash & table->size_mask];
@@ -382,8 +384,9 @@ lruhash_remove(struct lruhash* table, hashvalue_t hash, void* key)
        lock_quick_unlock(&bin->lock);
        /* finish removal */
        lock_rw_unlock(&entry->lock);
+       d = entry->data;
        (*table->delkeyfunc)(entry->key, table->cb_arg);
-       (*table->deldatafunc)(entry->data, table->cb_arg);
+       (*table->deldatafunc)(d, table->cb_arg);
 }
 
 
index c8df86501526296709f4f4b5749889efd097967b..76a939cf2e1dc7358ef392747d91f786fa08f121 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * util/storage/lrulash.h - hashtable, hash function, LRU keeping.
+ * util/storage/lruhash.h - hashtable, hash function, LRU keeping.
  *
  * Copyright (c) 2007, NLnet Labs. All rights reserved.
  *
diff --git a/util/storage/slabhash.c b/util/storage/slabhash.c
new file mode 100644 (file)
index 0000000..312e688
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+ * util/storage/slabhash.c - hashtable consisting of several smaller tables.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * Implementation of hash table that consists of smaller hash tables.
+ * It cannot grow, but that gives it the ability to have multiple
+ * locks. Also this means there are multiple LRU lists.
+ */
+
+#include "config.h"
+#include "util/storage/slabhash.h"
+
+struct slabhash* slabhash_create(size_t numtables, size_t start_size, 
+       size_t maxmem, lruhash_sizefunc_t sizefunc, 
+       lruhash_compfunc_t compfunc, lruhash_delkeyfunc_t delkeyfunc, 
+       lruhash_deldatafunc_t deldatafunc, void* arg)
+{
+       size_t i;
+       struct slabhash* sl = (struct slabhash*)calloc(1, 
+               sizeof(struct slabhash));
+       if(!sl) return NULL;
+       sl->size = numtables;
+       log_assert(sl->size > 0);
+       sl->array = (struct lruhash**)calloc(sl->size, sizeof(struct lruhash*));
+       if(!sl->array) {
+               free(sl);
+               return NULL;
+       }
+       sl->mask = (uint32_t)(sl->size - 1);
+       if(sl->mask == 0) {
+               sl->shift = 0;
+       } else {
+               log_assert( (sl->size & sl->mask) == 0 
+                       /* size must be power of 2 */ );
+               sl->shift = 0;
+               while(!(sl->mask & 0x80000000)) {
+                       sl->mask <<= 1;
+                       sl->shift ++;
+               }
+       }
+       for(i=0; i<sl->size; i++) {
+               sl->array[i] = lruhash_create(start_size, maxmem / sl->size,
+                       sizefunc, compfunc, delkeyfunc, deldatafunc, arg);
+               if(!sl->array[i]) {
+                       slabhash_delete(sl);
+                       return NULL;
+               }
+       }
+       return sl;
+}
+
+void slabhash_delete(struct slabhash* sl)
+{
+       if(!sl)
+               return;
+       if(sl->array) {
+               size_t i;
+               for(i=0; i<sl->size; i++)
+                       lruhash_delete(sl->array[i]);
+               free(sl->array);
+       }
+       free(sl);
+}
+
+/** helper routine to calculate the slabhash index */
+static unsigned int
+slab_idx(struct slabhash* sl, hashvalue_t hash)
+{
+       return ((hash & sl->mask) >> sl->shift);
+}
+
+void slabhash_insert(struct slabhash* sl, hashvalue_t hash, 
+       struct lruhash_entry* entry, void* data)
+{
+       lruhash_insert(sl->array[slab_idx(sl, hash)], hash, entry, data);
+}
+
+struct lruhash_entry* slabhash_lookup(struct slabhash* sl, 
+       hashvalue_t hash, void* key, int wr)
+{
+       return lruhash_lookup(sl->array[slab_idx(sl, hash)], hash, key, wr);
+}
+
+void slabhash_remove(struct slabhash* sl, hashvalue_t hash, void* key)
+{
+       lruhash_remove(sl->array[slab_idx(sl, hash)], hash, key);
+}
+
+void slabhash_status(struct slabhash* sl, const char* id, int extended)
+{
+       size_t i;
+       char num[17];
+       log_info("Slabhash %s: %d tables mask=%x shift=%d", 
+               id, sl->size, sl->mask, sl->shift);
+       for(i=0; i<sl->size; i++) {
+               snprintf(num, sizeof(num), "table %d", i);
+               lruhash_status(sl->array[i], num, extended);
+       }
+}
diff --git a/util/storage/slabhash.h b/util/storage/slabhash.h
new file mode 100644 (file)
index 0000000..2e9a740
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * util/storage/slabhash.h - hashtable consisting of several smaller tables.
+ *
+ * Copyright (c) 2007, NLnet Labs. All rights reserved.
+ *
+ * This software is open source.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 
+ * Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 
+ * Neither the name of the NLNET LABS nor the names of its contributors may
+ * be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * \file
+ *
+ * Hash table that consists of smaller hash tables.
+ * It cannot grow, but that gives it the ability to have multiple
+ * locks. Also this means there are multiple LRU lists.
+ */
+
+#ifndef UTIL_STORAGE_SLABHASH_H
+#define UTIL_STORAGE_SLABHASH_H
+#include "util/storage/lruhash.h"
+
+/**
+ * Hash table formed from several smaller ones. 
+ * None of the data inside the slabhash may be altered.
+ * Therefore, no locks are needed to access the structure.
+ */
+struct slabhash {
+       /** the size of the array - must be power of 2 */
+       size_t size;
+       /** size bitmask - uses high bits. */
+       uint32_t mask;
+       /** shift right this many bits to get index into array. */
+       unsigned int shift;
+       /** lookup array of hash tables */
+       struct lruhash** array;
+};
+
+/**
+ * Create new slabbed hash table.
+ * @param numtables: number of hash tables to use, other parameters used to
+ *     initialize these smaller hashtables.
+ * @param start_size: size of hashtable array at start, must be power of 2.
+ * @param maxmem: maximum amount of memory this table is allowed to use.
+ *     so every table gets maxmem/numtables to use for itself.
+ * @param sizefunc: calculates memory usage of entries.
+ * @param compfunc: compares entries, 0 on equality.
+ * @param delkeyfunc: deletes key.
+ * @param deldatafunc: deletes data. 
+ * @param arg: user argument that is passed to user function calls.
+ * @return: new hash table or NULL on malloc failure.
+ */
+struct slabhash* slabhash_create(size_t numtables, size_t start_size, 
+       size_t maxmem, lruhash_sizefunc_t sizefunc, 
+       lruhash_compfunc_t compfunc, lruhash_delkeyfunc_t delkeyfunc, 
+       lruhash_deldatafunc_t deldatafunc, void* arg);
+
+/**
+ * Delete hash table. Entries are all deleted.
+ * @param table: to delete.
+ */
+void slabhash_delete(struct slabhash* table);
+
+/**
+ * Insert a new element into the hashtable, uses lruhash_insert. 
+ * If key is already present data pointer in that entry is updated.
+ *
+ * @param table: hash table.
+ * @param hash: hash value. User calculates the hash.
+ * @param entry: identifies the entry.
+ *     If key already present, this entry->key is deleted immediately.
+ *     But entry->data is set to NULL before deletion, and put into
+ *     the existing entry. The data is then freed.
+ * @param data: the data.
+ */
+void slabhash_insert(struct slabhash* table, hashvalue_t hash, 
+       struct lruhash_entry* entry, void* data);
+
+/**
+ * Lookup an entry in the hashtable. Uses lruhash_lookup.
+ * At the end of the function you hold a (read/write)lock on the entry.
+ * The LRU is updated for the entry (if found).
+ * @param table: hash table.
+ * @param hash: hash of key.
+ * @param key: what to look for, compared against entries in overflow chain.
+ *    the hash value must be set, and must work with compare function.
+ * @param wr: set to true if you desire a writelock on the entry.
+ *    with a writelock you can update the data part.
+ * @return: pointer to the entry or NULL. The entry is locked.
+ *    The user must unlock the entry when done.
+ */
+struct lruhash_entry* slabhash_lookup(struct slabhash* table, 
+       hashvalue_t hash, void* key, int wr);
+
+/**
+ * Remove entry from hashtable. Does nothing if not found in hashtable.
+ * Delfunc is called for the entry. Uses lruhash_remove.
+ * @param table: hash table.
+ * @param hash: hash of key.
+ * @param key: what to look for. 
+ */
+void slabhash_remove(struct slabhash* table, hashvalue_t hash, void* key);
+
+/**
+ * Output debug info to the log as to state of the hash table.
+ * @param table: hash table.
+ * @param id: string printed with table to identify the hash table.
+ * @param extended: set to true to print statistics on overflow bin lengths.
+ */
+void slabhash_status(struct slabhash* table, const char* id, int extended);
+
+#endif /* UTIL_STORAGE_SLABHASH_H */