From: Wouter Wijngaards Date: Wed, 14 Mar 2007 10:42:50 +0000 (+0000) Subject: lruhash insert and lookup. X-Git-Tag: release-0.2~51 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3f61aca920f0f0304f0d065c1b6ff4afc860aa6c;p=thirdparty%2Funbound.git lruhash insert and lookup. git-svn-id: file:///svn/unbound/trunk@176 be551aaa-1e26-0410-a405-d3ace91eadb9 --- diff --git a/doc/Changelog b/doc/Changelog index 121d30648..fcda773a2 100644 --- a/doc/Changelog +++ b/doc/Changelog @@ -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. diff --git a/util/storage/lruhash.c b/util/storage/lruhash.c index 01a1b339e..998ff3abd 100644 --- a/util/storage/lruhash.c +++ b/util/storage/lruhash.c @@ -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; ilock); @@ -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; isize; 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; isize; 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; isize; 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) diff --git a/util/storage/lruhash.h b/util/storage/lruhash.h index f4cddc2b5..8fd8da7eb 100644 --- a/util/storage/lruhash.h +++ b/util/storage/lruhash.h @@ -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.