]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
lruhash insert and lookup.
authorWouter Wijngaards <wouter@nlnetlabs.nl>
Wed, 14 Mar 2007 10:42:50 +0000 (10:42 +0000)
committerWouter Wijngaards <wouter@nlnetlabs.nl>
Wed, 14 Mar 2007 10:42:50 +0000 (10:42 +0000)
git-svn-id: file:///svn/unbound/trunk@176 be551aaa-1e26-0410-a405-d3ace91eadb9

doc/Changelog
util/storage/lruhash.c
util/storage/lruhash.h

index 121d30648b22e428805e6559ddad05fc6640e608..fcda773a224a98da959feea3766e68e2ec4eb264 100644 (file)
@@ -1,3 +1,6 @@
+14 March 2007: Wouter
+       - hash table insert (and subroutines) and lookup implemented.
+
 13 March 2007: Wouter
        - lock_unprotect in checklocks.
        - util/storage/lruhash.h for LRU hash table structure.
index 01a1b339e89baa81ecf025b760116202bcaca427..998ff3abd5fafb4a751335ec492d219668f2fb84 100644 (file)
@@ -45,6 +45,9 @@
 
 /* ------ local helper functions ------------- */
 
+/** init the hash bins for the table. */
+static void bin_init(struct lruhash_bin* array, size_t size);
+
 /** delete the hash bin and entries inside it */
 static void bin_delete(struct lruhash* table, struct lruhash_bin* bin);
 
@@ -58,25 +61,83 @@ static void bin_delete(struct lruhash* table, struct lruhash_bin* bin);
 static struct lruhash_entry* bin_find_entry(struct lruhash* table, 
        struct lruhash_bin* bin, hashvalue_t hash, void* key);
 
+/**
+ * Remove entry from bin overflow chain.
+ * You must have locked the bin.
+ * @param bin: hash bin to look into.
+ * @param entry: entry ptr that needs removal.
+ */
+static void bin_overflow_remove(struct lruhash_bin* bin, 
+       struct lruhash_entry* entry);
+
+/**
+ * Split hash bin into two new ones. Based on increased size_mask.
+ * Caller must hold hash table lock.
+ * At the end the routine acquires all hashbin locks (in the old array).
+ * This makes it wait for other threads to finish with the bins.
+ * So the bins are ready to be deleted after this function.
+ * @param table: hash table with function pointers.
+ * @param newa: new increased array.
+ * @param newmask: new lookup mask.
+ */
+static void bin_split(struct lruhash* table, struct lruhash_bin* newa, 
+       int newmask);
+
 /** 
- * Try to make howmuch space available for a new entry into the table.
- * works by deleting old entries.
+ * Try to make space available by deleting old entries.
  * Assumes that the lock on the hashtable is being held by caller.
+ * Caller must not hold bin locks.
  * @param table: hash table.
- * @param howmuch: will try to make max-used at least this amount.
+ * @param list: list of entries that are to be deleted later.
+ *     Entries have been removed from the hash table and writelock is held.
  */
-static void reclaim_space(struct lruhash* table, size_t howmuch);
+static void reclaim_space(struct lruhash* table, struct lruhash_entry** list);
 
 /**
  * Grow the table lookup array. Becomes twice as large.
- * Caller must hold the hash table lock.
+ * Caller must hold the hash table lock. Must not hold any bin locks.
+ * Tries to grow, on malloc failure, nothing happened.
  * @param table: hash table.
- * @return false on error (malloc).
  */
-static int table_grow(struct lruhash* table);
+static void table_grow(struct lruhash* table);
+
+/**
+ * Touch entry, so it becomes the most recently used in the LRU list.
+ * Caller must hold hash table lock. The entry must be inserted already.
+ * @param table: hash table.
+ * @param entry: entry to make first in LRU.
+ */
+static void lru_touch(struct lruhash* table, struct lruhash_entry* entry);
+
+/**
+ * Put entry at front of lru. entry must be unlinked from lru.
+ * Caller must hold hash table lock.
+ * @param table: hash table with lru head and tail.
+ * @param entry: entry to make most recently used.
+ */
+static void lru_front(struct lruhash* table, struct lruhash_entry* entry);
+
+/**
+ * Remove entry from lru list.
+ * Caller must hold hash table lock.
+ * @param table: hash table with lru head and tail.
+ * @param entry: entry to remove from lru.
+ */
+static void lru_remove(struct lruhash* table, struct lruhash_entry* entry);
 
 /* ------ end local helper functions --------- */
 
+static void
+bin_init(struct lruhash_bin* array, size_t size)
+{
+       size_t i;
+       for(i=0; i<size; i++) {
+               lock_quick_init(&array[i].lock);
+               lock_protect(&array[i].lock, &array[i], 
+                       sizeof(struct lruhash_bin));
+       }
+}
+
 struct lruhash* lruhash_create(size_t start_size, size_t maxmem,
         lruhash_sizefunc_t sizefunc, lruhash_compfunc_t compfunc,
        lruhash_delkeyfunc_t delkeyfunc, lruhash_deldatafunc_t deldatafunc,
@@ -84,7 +145,6 @@ struct lruhash* lruhash_create(size_t start_size, size_t maxmem,
 {
        struct lruhash* table = (struct lruhash*)calloc(1, 
                sizeof(struct lruhash));
-       size_t i;
        if(!table)
                return NULL;
        lock_quick_init(&table->lock);
@@ -94,7 +154,7 @@ struct lruhash* lruhash_create(size_t start_size, size_t maxmem,
        table->deldatafunc = deldatafunc;
        table->cb_arg = arg;
        table->size = start_size;
-       table->size_mask = start_size-1;
+       table->size_mask = (int)(start_size-1);
        table->lru_start = NULL;
        table->lru_end = NULL;
        table->num = 0;
@@ -106,9 +166,7 @@ struct lruhash* lruhash_create(size_t start_size, size_t maxmem,
                free(table);
                return NULL;
        }
-       for(i=0; i<table->size; i++) {
-               lock_quick_init(&table->array[i].lock);
-       }
+       bin_init(table->array, table->size);
        lock_protect(&table->lock, table, sizeof(*table));
        lock_protect(&table->lock, table->array, 
                table->size*sizeof(struct lruhash_bin));
@@ -131,6 +189,34 @@ static void bin_delete(struct lruhash* table, struct lruhash_bin* bin)
        }
 }
 
+static void 
+bin_split(struct lruhash* table, struct lruhash_bin* newa, 
+       int newmask)
+{
+       size_t i;
+       struct lruhash_entry *p, *np;
+       int newbin;
+       /* move entries to new table. Notice that since hash x is mapped to
+        * bin x & mask, and new mask uses one more bit, so all entries in
+        * one bin will go into the old bin or bin | newbit */
+       /* newbit = newmask - table->size_mask; */
+       /* so, really, this task could also be threaded, per bin. */
+       /* LRU list is not changed */
+       for(i=0; i<table->size; i++)
+       {
+               lock_quick_lock(&table->array[i].lock);
+               p = table->array[i].overflow_list;
+               while(p) {
+                       np = p->overflow_next;
+                       /* link into correct new bin */
+                       newbin = p->hash & newmask;
+                       p->overflow_next = newa[newbin].overflow_list;
+                       newa[newbin].overflow_list = p;
+                       p=np;
+               }
+       }
+}
+
 void lruhash_delete(struct lruhash* table)
 {
        size_t i;
@@ -145,35 +231,146 @@ void lruhash_delete(struct lruhash* table)
 }
 
 static void 
-reclaim_space(struct lruhash* table, size_t howmuch)
+bin_overflow_remove(struct lruhash_bin* bin, struct lruhash_entry* entry)
 {
+       struct lruhash_entry* p = bin->overflow_list;
+       struct lruhash_entry** prevp = &bin->overflow_list;
+       while(p) {
+               if(p == entry) {
+                       *prevp = p->overflow_next;
+                       return;
+               }
+               prevp = &p->overflow_next;
+               p = p->overflow_next;
+       }
+}
+
+static void 
+reclaim_space(struct lruhash* table, struct lruhash_entry** list)
+{
+       struct lruhash_entry* d;
+       struct lruhash_bin* bin;
+       log_assert(table);
+       /* does not delete MRU entry, so table will not be empty. */
+       while(table->num > 1 && table->space_used > table->space_max) {
+               /* notice that since we hold the hashtable lock, nobody
+                  can change the lru chain. So it cannot be deleted underneath
+                  us. We still need the hashbin and entry write lock to make 
+                  sure we flush all users away from the entry. 
+                  which is unlikely, since it is LRU, if someone got a rdlock
+                  it would be moved to front, but to be sure. */
+               d = table->lru_end;
+               /* specialised, delete from end of double linked list,
+                  and we know num>1, so there is a previous lru entry. */
+               log_assert(d && d->lru_prev);
+               table->lru_end = d->lru_prev;
+               d->lru_prev->lru_next = NULL;
+               /* schedule entry for deletion */
+               bin = &table->array[d->hash & table->size_mask];
+               table->num --;
+               lock_quick_lock(&bin->lock);
+               bin_overflow_remove(bin, d);
+               d->overflow_next = *list;
+               *list = d;
+               lock_rw_wrlock(&d->lock);
+               table->space_used -= table->sizefunc(d->key, d->data);
+               lock_quick_unlock(&bin->lock);
+       }
 }
 
 static struct lruhash_entry* 
 bin_find_entry(struct lruhash* table, 
        struct lruhash_bin* bin, hashvalue_t hash, void* key)
 {
+       struct lruhash_entry* p = bin->overflow_list;
+       while(p) {
+               if(p->hash == hash && table->compfunc(p->key, key) == 0)
+                       return p;
+               p = p->overflow_next;
+       }
        return NULL;
 }
 
-static int table_grow(struct lruhash* table)
+static void 
+table_grow(struct lruhash* table)
+{
+       struct lruhash_bin* newa;
+       int newmask;
+       size_t i;
+       if(table->size_mask == (int)(((size_t)-1)>>1)) {
+               log_err("hash array malloc: size_t too small");
+               return;
+       }
+       /* try to allocate new array, if not fail */
+       newa = calloc(table->size*2, sizeof(struct lruhash_bin));
+       if(!newa) {
+               log_err("hash grow: malloc failed");
+               /* continue with smaller array. Though its slower. */
+               return;
+       }
+       bin_init(newa, table->size*2);
+       newmask = (table->size_mask << 1) | 1;
+       bin_split(table, newa, newmask);
+       /* delete the old bins */
+       lock_unprotect(&table->lock, table->array);
+       for(i=0; i<table->size; i++) {
+               lock_quick_unlock(&table->array[i].lock);
+               lock_quick_destroy(&table->array[i].lock);
+       }
+       free(table->array);
+       
+       table->size *= 2;
+       table->size_mask = newmask;
+       table->array = newa;
+       lock_protect(&table->lock, table->array, 
+               table->size*sizeof(struct lruhash_bin));
+       return;
+}
+
+static void 
+lru_front(struct lruhash* table, struct lruhash_entry* entry)
+{
+       entry->lru_prev = NULL;
+       entry->lru_next = table->lru_start;
+       if(!table->lru_start)
+               table->lru_end = entry;
+       else    table->lru_start->lru_prev = entry;
+       table->lru_start = entry;
+}
+
+static void 
+lru_remove(struct lruhash* table, struct lruhash_entry* entry)
+{
+       if(entry->lru_prev)
+               entry->lru_prev->lru_next = entry->lru_next;
+       else    table->lru_start = entry->lru_next;
+       if(entry->lru_next)
+               entry->lru_next->lru_prev = entry->lru_prev;
+       else    table->lru_end = entry->lru_prev;
+}
+
+static void 
+lru_touch(struct lruhash* table, struct lruhash_entry* entry)
 {
-       return 0;
+       log_assert(table && entry);
+       if(entry == table->lru_start)
+               return; /* nothing to do */
+       /* remove from current lru position */
+       lru_remove(table, entry);
+       /* add at front */
+       lru_front(table, entry);
 }
 
 void lruhash_insert(struct lruhash* table, hashvalue_t hash,
         struct lruhash_entry* entry, void* data)
 {
        struct lruhash_bin* bin;
-       struct lruhash_entry* found;
+       struct lruhash_entry* found, *reclaimlist=NULL;
        size_t need_size;
-       lock_quick_lock(&table->lock);
-       /* see if table memory exceeded and needs clipping. */
        need_size = table->sizefunc(entry->key, data);
-       if(table->space_used + need_size > table->space_max)
-               reclaim_space(table, need_size);
 
        /* find bin */
+       lock_quick_lock(&table->lock);
        bin = &table->array[hash & table->size_mask];
        lock_quick_lock(&bin->lock);
 
@@ -182,31 +379,55 @@ void lruhash_insert(struct lruhash* table, hashvalue_t hash,
                /* if not: add to bin */
                entry->overflow_next = bin->overflow_list;
                bin->overflow_list = entry;
+               lru_front(table, entry);
                table->num++;
                table->space_used += need_size;
        } else {
                /* if so: update data */
                table->space_used += need_size -
                        (*table->sizefunc)(found->key, found->data);
-               entry->data = NULL;
                (*table->delkeyfunc)(entry->key, table->cb_arg);
                (*table->deldatafunc)(found->data, table->cb_arg);
                found->data = data;
+               lru_touch(table, found);
        }
        lock_quick_unlock(&bin->lock);
+       if(table->space_used > table->space_max)
+               reclaim_space(table, &reclaimlist);
+       if(table->num >= table->size)
+               table_grow(table);
+       lock_quick_unlock(&table->lock);
 
-       /* see if table lookup must be grown up */
-       if(table->num == table->size) {
-               if(!table_grow(table))
-                       log_err("hash grow: malloc failed");
-                       /* continue with smaller array. Slower. */
+       /* finish reclaim if any (outside of critical region) */
+       while(reclaimlist) {
+               struct lruhash_entry* n = reclaimlist->overflow_next;
+               lock_rw_unlock(&reclaimlist->lock);
+               (*table->delkeyfunc)(reclaimlist->key, table->cb_arg);
+               (*table->deldatafunc)(reclaimlist->data, table->cb_arg);
+               reclaimlist = n;
        }
-       lock_quick_unlock(&table->lock);
 }
 
-struct lruhash_entry* lruhash_lookup(struct lruhash* table, void* key, int wr)
+struct lruhash_entry* lruhash_lookup(struct lruhash* table, hashvalue_t hash,
+       void* key, int wr)
 {
-       return NULL;
+       struct lruhash_entry* entry = NULL;
+       struct lruhash_bin* bin;
+
+       lock_quick_lock(&table->lock);
+       bin = &table->array[hash & table->size_mask];
+       lock_quick_lock(&bin->lock);
+       if((entry=bin_find_entry(table, bin, hash, key)))
+               lru_touch(table, entry);
+       lock_quick_unlock(&table->lock);
+
+       if(entry) {
+               if(wr)
+                       lock_rw_wrlock(&entry->lock);
+               else    lock_rw_rdlock(&entry->lock);
+       }
+       lock_quick_unlock(&bin->lock);
+       return entry;
 }
 
 void lruhash_remove(struct lruhash* table, void* key)
index f4cddc2b5d40f79b203823ae9b8d98355e70f96a..8fd8da7eb54503c35224b8e731c9a6a4f3926e94 100644 (file)
@@ -194,13 +194,13 @@ struct lruhash_entry {
         * You need to delete it to change hash or key.
         */
        lock_rw_t lock;
-       /** next entry in overflow chain */
+       /** next entry in overflow chain. Covered by hashlock and binlock. */
        struct lruhash_entry* overflow_next;
-       /** next entry in lru chain */
+       /** next entry in lru chain. covered by hashlock. */
        struct lruhash_entry* lru_next;
-       /** prev entry in lru chain */
+       /** prev entry in lru chain. covered by hashlock. */
        struct lruhash_entry* lru_prev;
-       /** hash value of the key */
+       /** hash value of the key. It may not change, until entry deleted. */
        hashvalue_t hash;
        /** key */
        void* key;
@@ -255,6 +255,7 @@ void lruhash_insert(struct lruhash* table, hashvalue_t hash,
  * 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.
@@ -262,7 +263,8 @@ void lruhash_insert(struct lruhash* table, hashvalue_t hash,
  * @return: pointer to the entry or NULL. The entry is locked.
  *    The user must unlock the entry when done.
  */
-struct lruhash_entry* lruhash_lookup(struct lruhash* table, void* key, int wr);
+struct lruhash_entry* lruhash_lookup(struct lruhash* table, hashvalue_t hash, 
+       void* key, int wr);
 
 /**
  * Remove entry from hashtable. Does nothing if not found in hashtable.