1b7579f77SDag-Erling Smørgrav /* 2b7579f77SDag-Erling Smørgrav * util/storage/lruhash.c - hashtable, hash function, LRU keeping. 3b7579f77SDag-Erling Smørgrav * 4b7579f77SDag-Erling Smørgrav * Copyright (c) 2007, NLnet Labs. All rights reserved. 5b7579f77SDag-Erling Smørgrav * 6b7579f77SDag-Erling Smørgrav * This software is open source. 7b7579f77SDag-Erling Smørgrav * 8b7579f77SDag-Erling Smørgrav * Redistribution and use in source and binary forms, with or without 9b7579f77SDag-Erling Smørgrav * modification, are permitted provided that the following conditions 10b7579f77SDag-Erling Smørgrav * are met: 11b7579f77SDag-Erling Smørgrav * 12b7579f77SDag-Erling Smørgrav * Redistributions of source code must retain the above copyright notice, 13b7579f77SDag-Erling Smørgrav * this list of conditions and the following disclaimer. 14b7579f77SDag-Erling Smørgrav * 15b7579f77SDag-Erling Smørgrav * Redistributions in binary form must reproduce the above copyright notice, 16b7579f77SDag-Erling Smørgrav * this list of conditions and the following disclaimer in the documentation 17b7579f77SDag-Erling Smørgrav * and/or other materials provided with the distribution. 18b7579f77SDag-Erling Smørgrav * 19b7579f77SDag-Erling Smørgrav * Neither the name of the NLNET LABS nor the names of its contributors may 20b7579f77SDag-Erling Smørgrav * be used to endorse or promote products derived from this software without 21b7579f77SDag-Erling Smørgrav * specific prior written permission. 22b7579f77SDag-Erling Smørgrav * 23b7579f77SDag-Erling Smørgrav * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 2417d15b25SDag-Erling Smørgrav * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 2517d15b25SDag-Erling Smørgrav * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 2617d15b25SDag-Erling Smørgrav * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 2717d15b25SDag-Erling Smørgrav * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 2817d15b25SDag-Erling Smørgrav * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 2917d15b25SDag-Erling Smørgrav * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 3017d15b25SDag-Erling Smørgrav * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 3117d15b25SDag-Erling Smørgrav * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 3217d15b25SDag-Erling Smørgrav * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 3317d15b25SDag-Erling Smørgrav * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34b7579f77SDag-Erling Smørgrav */ 35b7579f77SDag-Erling Smørgrav 36b7579f77SDag-Erling Smørgrav /** 37b7579f77SDag-Erling Smørgrav * \file 38b7579f77SDag-Erling Smørgrav * 39b7579f77SDag-Erling Smørgrav * This file contains a hashtable with LRU keeping of entries. 40b7579f77SDag-Erling Smørgrav * 41b7579f77SDag-Erling Smørgrav */ 42b7579f77SDag-Erling Smørgrav 43b7579f77SDag-Erling Smørgrav #include "config.h" 44b7579f77SDag-Erling Smørgrav #include "util/storage/lruhash.h" 45b7579f77SDag-Erling Smørgrav #include "util/fptr_wlist.h" 46b7579f77SDag-Erling Smørgrav 47b7579f77SDag-Erling Smørgrav void 48b7579f77SDag-Erling Smørgrav bin_init(struct lruhash_bin* array, size_t size) 49b7579f77SDag-Erling Smørgrav { 50b7579f77SDag-Erling Smørgrav size_t i; 51b7579f77SDag-Erling Smørgrav #ifdef THREADS_DISABLED 52b7579f77SDag-Erling Smørgrav (void)array; 53b7579f77SDag-Erling Smørgrav #endif 54b7579f77SDag-Erling Smørgrav for(i=0; i<size; i++) { 55b7579f77SDag-Erling Smørgrav lock_quick_init(&array[i].lock); 56b7579f77SDag-Erling Smørgrav lock_protect(&array[i].lock, &array[i], 57b7579f77SDag-Erling Smørgrav sizeof(struct lruhash_bin)); 58b7579f77SDag-Erling Smørgrav } 59b7579f77SDag-Erling Smørgrav } 60b7579f77SDag-Erling Smørgrav 61b7579f77SDag-Erling Smørgrav struct lruhash* 623005e0a3SDag-Erling Smørgrav lruhash_create(size_t start_size, size_t maxmem, 633005e0a3SDag-Erling Smørgrav lruhash_sizefunc_type sizefunc, lruhash_compfunc_type compfunc, 643005e0a3SDag-Erling Smørgrav lruhash_delkeyfunc_type delkeyfunc, 653005e0a3SDag-Erling Smørgrav lruhash_deldatafunc_type deldatafunc, void* arg) 66b7579f77SDag-Erling Smørgrav { 67b7579f77SDag-Erling Smørgrav struct lruhash* table = (struct lruhash*)calloc(1, 68b7579f77SDag-Erling Smørgrav sizeof(struct lruhash)); 69b7579f77SDag-Erling Smørgrav if(!table) 70b7579f77SDag-Erling Smørgrav return NULL; 71b7579f77SDag-Erling Smørgrav lock_quick_init(&table->lock); 72b7579f77SDag-Erling Smørgrav table->sizefunc = sizefunc; 73b7579f77SDag-Erling Smørgrav table->compfunc = compfunc; 74b7579f77SDag-Erling Smørgrav table->delkeyfunc = delkeyfunc; 75b7579f77SDag-Erling Smørgrav table->deldatafunc = deldatafunc; 76b7579f77SDag-Erling Smørgrav table->cb_arg = arg; 77b7579f77SDag-Erling Smørgrav table->size = start_size; 78b7579f77SDag-Erling Smørgrav table->size_mask = (int)(start_size-1); 79b7579f77SDag-Erling Smørgrav table->lru_start = NULL; 80b7579f77SDag-Erling Smørgrav table->lru_end = NULL; 81b7579f77SDag-Erling Smørgrav table->num = 0; 82b7579f77SDag-Erling Smørgrav table->space_used = 0; 83b7579f77SDag-Erling Smørgrav table->space_max = maxmem; 84b7579f77SDag-Erling Smørgrav table->array = calloc(table->size, sizeof(struct lruhash_bin)); 85b7579f77SDag-Erling Smørgrav if(!table->array) { 86b7579f77SDag-Erling Smørgrav lock_quick_destroy(&table->lock); 87b7579f77SDag-Erling Smørgrav free(table); 88b7579f77SDag-Erling Smørgrav return NULL; 89b7579f77SDag-Erling Smørgrav } 90b7579f77SDag-Erling Smørgrav bin_init(table->array, table->size); 91b7579f77SDag-Erling Smørgrav lock_protect(&table->lock, table, sizeof(*table)); 92b7579f77SDag-Erling Smørgrav lock_protect(&table->lock, table->array, 93b7579f77SDag-Erling Smørgrav table->size*sizeof(struct lruhash_bin)); 94b7579f77SDag-Erling Smørgrav return table; 95b7579f77SDag-Erling Smørgrav } 96b7579f77SDag-Erling Smørgrav 97b7579f77SDag-Erling Smørgrav void 98b7579f77SDag-Erling Smørgrav bin_delete(struct lruhash* table, struct lruhash_bin* bin) 99b7579f77SDag-Erling Smørgrav { 100b7579f77SDag-Erling Smørgrav struct lruhash_entry* p, *np; 101b7579f77SDag-Erling Smørgrav void *d; 102b7579f77SDag-Erling Smørgrav if(!bin) 103b7579f77SDag-Erling Smørgrav return; 104b7579f77SDag-Erling Smørgrav lock_quick_destroy(&bin->lock); 105b7579f77SDag-Erling Smørgrav p = bin->overflow_list; 106b7579f77SDag-Erling Smørgrav bin->overflow_list = NULL; 107b7579f77SDag-Erling Smørgrav while(p) { 108b7579f77SDag-Erling Smørgrav np = p->overflow_next; 109b7579f77SDag-Erling Smørgrav d = p->data; 110b7579f77SDag-Erling Smørgrav (*table->delkeyfunc)(p->key, table->cb_arg); 111b7579f77SDag-Erling Smørgrav (*table->deldatafunc)(d, table->cb_arg); 112b7579f77SDag-Erling Smørgrav p = np; 113b7579f77SDag-Erling Smørgrav } 114b7579f77SDag-Erling Smørgrav } 115b7579f77SDag-Erling Smørgrav 116b7579f77SDag-Erling Smørgrav void 117b7579f77SDag-Erling Smørgrav bin_split(struct lruhash* table, struct lruhash_bin* newa, 118b7579f77SDag-Erling Smørgrav int newmask) 119b7579f77SDag-Erling Smørgrav { 120b7579f77SDag-Erling Smørgrav size_t i; 121b7579f77SDag-Erling Smørgrav struct lruhash_entry *p, *np; 122b7579f77SDag-Erling Smørgrav struct lruhash_bin* newbin; 123b7579f77SDag-Erling Smørgrav /* move entries to new table. Notice that since hash x is mapped to 124b7579f77SDag-Erling Smørgrav * bin x & mask, and new mask uses one more bit, so all entries in 125b7579f77SDag-Erling Smørgrav * one bin will go into the old bin or bin | newbit */ 126b7579f77SDag-Erling Smørgrav #ifndef THREADS_DISABLED 127b7579f77SDag-Erling Smørgrav int newbit = newmask - table->size_mask; 128b7579f77SDag-Erling Smørgrav #endif 129b7579f77SDag-Erling Smørgrav /* so, really, this task could also be threaded, per bin. */ 130b7579f77SDag-Erling Smørgrav /* LRU list is not changed */ 131b7579f77SDag-Erling Smørgrav for(i=0; i<table->size; i++) 132b7579f77SDag-Erling Smørgrav { 133b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->array[i].lock); 134b7579f77SDag-Erling Smørgrav p = table->array[i].overflow_list; 135b7579f77SDag-Erling Smørgrav /* lock both destination bins */ 136b7579f77SDag-Erling Smørgrav lock_quick_lock(&newa[i].lock); 137b7579f77SDag-Erling Smørgrav lock_quick_lock(&newa[newbit|i].lock); 138b7579f77SDag-Erling Smørgrav while(p) { 139b7579f77SDag-Erling Smørgrav np = p->overflow_next; 140b7579f77SDag-Erling Smørgrav /* link into correct new bin */ 141b7579f77SDag-Erling Smørgrav newbin = &newa[p->hash & newmask]; 142b7579f77SDag-Erling Smørgrav p->overflow_next = newbin->overflow_list; 143b7579f77SDag-Erling Smørgrav newbin->overflow_list = p; 144b7579f77SDag-Erling Smørgrav p=np; 145b7579f77SDag-Erling Smørgrav } 146b7579f77SDag-Erling Smørgrav lock_quick_unlock(&newa[i].lock); 147b7579f77SDag-Erling Smørgrav lock_quick_unlock(&newa[newbit|i].lock); 148b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->array[i].lock); 149b7579f77SDag-Erling Smørgrav } 150b7579f77SDag-Erling Smørgrav } 151b7579f77SDag-Erling Smørgrav 152b7579f77SDag-Erling Smørgrav void 153b7579f77SDag-Erling Smørgrav lruhash_delete(struct lruhash* table) 154b7579f77SDag-Erling Smørgrav { 155b7579f77SDag-Erling Smørgrav size_t i; 156b7579f77SDag-Erling Smørgrav if(!table) 157b7579f77SDag-Erling Smørgrav return; 158b7579f77SDag-Erling Smørgrav /* delete lock on hashtable to force check its OK */ 159b7579f77SDag-Erling Smørgrav lock_quick_destroy(&table->lock); 160b7579f77SDag-Erling Smørgrav for(i=0; i<table->size; i++) 161b7579f77SDag-Erling Smørgrav bin_delete(table, &table->array[i]); 162b7579f77SDag-Erling Smørgrav free(table->array); 163b7579f77SDag-Erling Smørgrav free(table); 164b7579f77SDag-Erling Smørgrav } 165b7579f77SDag-Erling Smørgrav 166b7579f77SDag-Erling Smørgrav void 167b7579f77SDag-Erling Smørgrav bin_overflow_remove(struct lruhash_bin* bin, struct lruhash_entry* entry) 168b7579f77SDag-Erling Smørgrav { 169b7579f77SDag-Erling Smørgrav struct lruhash_entry* p = bin->overflow_list; 170b7579f77SDag-Erling Smørgrav struct lruhash_entry** prevp = &bin->overflow_list; 171b7579f77SDag-Erling Smørgrav while(p) { 172b7579f77SDag-Erling Smørgrav if(p == entry) { 173b7579f77SDag-Erling Smørgrav *prevp = p->overflow_next; 174b7579f77SDag-Erling Smørgrav return; 175b7579f77SDag-Erling Smørgrav } 176b7579f77SDag-Erling Smørgrav prevp = &p->overflow_next; 177b7579f77SDag-Erling Smørgrav p = p->overflow_next; 178b7579f77SDag-Erling Smørgrav } 179b7579f77SDag-Erling Smørgrav } 180b7579f77SDag-Erling Smørgrav 181b7579f77SDag-Erling Smørgrav void 182b7579f77SDag-Erling Smørgrav reclaim_space(struct lruhash* table, struct lruhash_entry** list) 183b7579f77SDag-Erling Smørgrav { 184b7579f77SDag-Erling Smørgrav struct lruhash_entry* d; 185b7579f77SDag-Erling Smørgrav struct lruhash_bin* bin; 186b7579f77SDag-Erling Smørgrav log_assert(table); 187b7579f77SDag-Erling Smørgrav /* does not delete MRU entry, so table will not be empty. */ 188b7579f77SDag-Erling Smørgrav while(table->num > 1 && table->space_used > table->space_max) { 189b7579f77SDag-Erling Smørgrav /* notice that since we hold the hashtable lock, nobody 190b7579f77SDag-Erling Smørgrav can change the lru chain. So it cannot be deleted underneath 191b7579f77SDag-Erling Smørgrav us. We still need the hashbin and entry write lock to make 192b7579f77SDag-Erling Smørgrav sure we flush all users away from the entry. 193b7579f77SDag-Erling Smørgrav which is unlikely, since it is LRU, if someone got a rdlock 194b7579f77SDag-Erling Smørgrav it would be moved to front, but to be sure. */ 195b7579f77SDag-Erling Smørgrav d = table->lru_end; 196b7579f77SDag-Erling Smørgrav /* specialised, delete from end of double linked list, 197b7579f77SDag-Erling Smørgrav and we know num>1, so there is a previous lru entry. */ 198b7579f77SDag-Erling Smørgrav log_assert(d && d->lru_prev); 199b7579f77SDag-Erling Smørgrav table->lru_end = d->lru_prev; 200b7579f77SDag-Erling Smørgrav d->lru_prev->lru_next = NULL; 201b7579f77SDag-Erling Smørgrav /* schedule entry for deletion */ 202b7579f77SDag-Erling Smørgrav bin = &table->array[d->hash & table->size_mask]; 203b7579f77SDag-Erling Smørgrav table->num --; 204b7579f77SDag-Erling Smørgrav lock_quick_lock(&bin->lock); 205b7579f77SDag-Erling Smørgrav bin_overflow_remove(bin, d); 206b7579f77SDag-Erling Smørgrav d->overflow_next = *list; 207b7579f77SDag-Erling Smørgrav *list = d; 208b7579f77SDag-Erling Smørgrav lock_rw_wrlock(&d->lock); 209b7579f77SDag-Erling Smørgrav table->space_used -= table->sizefunc(d->key, d->data); 210b7579f77SDag-Erling Smørgrav if(table->markdelfunc) 211b7579f77SDag-Erling Smørgrav (*table->markdelfunc)(d->key); 212b7579f77SDag-Erling Smørgrav lock_rw_unlock(&d->lock); 213b7579f77SDag-Erling Smørgrav lock_quick_unlock(&bin->lock); 214b7579f77SDag-Erling Smørgrav } 215b7579f77SDag-Erling Smørgrav } 216b7579f77SDag-Erling Smørgrav 217b7579f77SDag-Erling Smørgrav struct lruhash_entry* 218b7579f77SDag-Erling Smørgrav bin_find_entry(struct lruhash* table, 2193005e0a3SDag-Erling Smørgrav struct lruhash_bin* bin, hashvalue_type hash, void* key) 220b7579f77SDag-Erling Smørgrav { 221b7579f77SDag-Erling Smørgrav struct lruhash_entry* p = bin->overflow_list; 222b7579f77SDag-Erling Smørgrav while(p) { 223b7579f77SDag-Erling Smørgrav if(p->hash == hash && table->compfunc(p->key, key) == 0) 224b7579f77SDag-Erling Smørgrav return p; 225b7579f77SDag-Erling Smørgrav p = p->overflow_next; 226b7579f77SDag-Erling Smørgrav } 227b7579f77SDag-Erling Smørgrav return NULL; 228b7579f77SDag-Erling Smørgrav } 229b7579f77SDag-Erling Smørgrav 230b7579f77SDag-Erling Smørgrav void 231b7579f77SDag-Erling Smørgrav table_grow(struct lruhash* table) 232b7579f77SDag-Erling Smørgrav { 233b7579f77SDag-Erling Smørgrav struct lruhash_bin* newa; 234b7579f77SDag-Erling Smørgrav int newmask; 235b7579f77SDag-Erling Smørgrav size_t i; 236b7579f77SDag-Erling Smørgrav if(table->size_mask == (int)(((size_t)-1)>>1)) { 237b7579f77SDag-Erling Smørgrav log_err("hash array malloc: size_t too small"); 238b7579f77SDag-Erling Smørgrav return; 239b7579f77SDag-Erling Smørgrav } 240b7579f77SDag-Erling Smørgrav /* try to allocate new array, if not fail */ 241b7579f77SDag-Erling Smørgrav newa = calloc(table->size*2, sizeof(struct lruhash_bin)); 242b7579f77SDag-Erling Smørgrav if(!newa) { 243b7579f77SDag-Erling Smørgrav log_err("hash grow: malloc failed"); 244b7579f77SDag-Erling Smørgrav /* continue with smaller array. Though its slower. */ 245b7579f77SDag-Erling Smørgrav return; 246b7579f77SDag-Erling Smørgrav } 247b7579f77SDag-Erling Smørgrav bin_init(newa, table->size*2); 248b7579f77SDag-Erling Smørgrav newmask = (table->size_mask << 1) | 1; 249b7579f77SDag-Erling Smørgrav bin_split(table, newa, newmask); 250b7579f77SDag-Erling Smørgrav /* delete the old bins */ 251b7579f77SDag-Erling Smørgrav lock_unprotect(&table->lock, table->array); 252b7579f77SDag-Erling Smørgrav for(i=0; i<table->size; i++) { 253b7579f77SDag-Erling Smørgrav lock_quick_destroy(&table->array[i].lock); 254b7579f77SDag-Erling Smørgrav } 255b7579f77SDag-Erling Smørgrav free(table->array); 256b7579f77SDag-Erling Smørgrav 257b7579f77SDag-Erling Smørgrav table->size *= 2; 258b7579f77SDag-Erling Smørgrav table->size_mask = newmask; 259b7579f77SDag-Erling Smørgrav table->array = newa; 260b7579f77SDag-Erling Smørgrav lock_protect(&table->lock, table->array, 261b7579f77SDag-Erling Smørgrav table->size*sizeof(struct lruhash_bin)); 262b7579f77SDag-Erling Smørgrav return; 263b7579f77SDag-Erling Smørgrav } 264b7579f77SDag-Erling Smørgrav 265b7579f77SDag-Erling Smørgrav void 266b7579f77SDag-Erling Smørgrav lru_front(struct lruhash* table, struct lruhash_entry* entry) 267b7579f77SDag-Erling Smørgrav { 268b7579f77SDag-Erling Smørgrav entry->lru_prev = NULL; 269b7579f77SDag-Erling Smørgrav entry->lru_next = table->lru_start; 270b7579f77SDag-Erling Smørgrav if(!table->lru_start) 271b7579f77SDag-Erling Smørgrav table->lru_end = entry; 272b7579f77SDag-Erling Smørgrav else table->lru_start->lru_prev = entry; 273b7579f77SDag-Erling Smørgrav table->lru_start = entry; 274b7579f77SDag-Erling Smørgrav } 275b7579f77SDag-Erling Smørgrav 276b7579f77SDag-Erling Smørgrav void 277b7579f77SDag-Erling Smørgrav lru_remove(struct lruhash* table, struct lruhash_entry* entry) 278b7579f77SDag-Erling Smørgrav { 279b7579f77SDag-Erling Smørgrav if(entry->lru_prev) 280b7579f77SDag-Erling Smørgrav entry->lru_prev->lru_next = entry->lru_next; 281b7579f77SDag-Erling Smørgrav else table->lru_start = entry->lru_next; 282b7579f77SDag-Erling Smørgrav if(entry->lru_next) 283b7579f77SDag-Erling Smørgrav entry->lru_next->lru_prev = entry->lru_prev; 284b7579f77SDag-Erling Smørgrav else table->lru_end = entry->lru_prev; 285b7579f77SDag-Erling Smørgrav } 286b7579f77SDag-Erling Smørgrav 287b7579f77SDag-Erling Smørgrav void 288b7579f77SDag-Erling Smørgrav lru_touch(struct lruhash* table, struct lruhash_entry* entry) 289b7579f77SDag-Erling Smørgrav { 290b7579f77SDag-Erling Smørgrav log_assert(table && entry); 291b7579f77SDag-Erling Smørgrav if(entry == table->lru_start) 292b7579f77SDag-Erling Smørgrav return; /* nothing to do */ 293b7579f77SDag-Erling Smørgrav /* remove from current lru position */ 294b7579f77SDag-Erling Smørgrav lru_remove(table, entry); 295b7579f77SDag-Erling Smørgrav /* add at front */ 296b7579f77SDag-Erling Smørgrav lru_front(table, entry); 297b7579f77SDag-Erling Smørgrav } 298b7579f77SDag-Erling Smørgrav 299b7579f77SDag-Erling Smørgrav void 3003005e0a3SDag-Erling Smørgrav lruhash_insert(struct lruhash* table, hashvalue_type hash, 301b7579f77SDag-Erling Smørgrav struct lruhash_entry* entry, void* data, void* cb_arg) 302b7579f77SDag-Erling Smørgrav { 303b7579f77SDag-Erling Smørgrav struct lruhash_bin* bin; 304b7579f77SDag-Erling Smørgrav struct lruhash_entry* found, *reclaimlist=NULL; 305b7579f77SDag-Erling Smørgrav size_t need_size; 306b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc)); 307b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); 308b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); 309b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc)); 310b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); 311b7579f77SDag-Erling Smørgrav need_size = table->sizefunc(entry->key, data); 312b7579f77SDag-Erling Smørgrav if(cb_arg == NULL) cb_arg = table->cb_arg; 313b7579f77SDag-Erling Smørgrav 314b7579f77SDag-Erling Smørgrav /* find bin */ 315b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->lock); 316b7579f77SDag-Erling Smørgrav bin = &table->array[hash & table->size_mask]; 317b7579f77SDag-Erling Smørgrav lock_quick_lock(&bin->lock); 318b7579f77SDag-Erling Smørgrav 319b7579f77SDag-Erling Smørgrav /* see if entry exists already */ 320b7579f77SDag-Erling Smørgrav if(!(found=bin_find_entry(table, bin, hash, entry->key))) { 321b7579f77SDag-Erling Smørgrav /* if not: add to bin */ 322b7579f77SDag-Erling Smørgrav entry->overflow_next = bin->overflow_list; 323b7579f77SDag-Erling Smørgrav bin->overflow_list = entry; 324b7579f77SDag-Erling Smørgrav lru_front(table, entry); 325b7579f77SDag-Erling Smørgrav table->num++; 326b7579f77SDag-Erling Smørgrav table->space_used += need_size; 327b7579f77SDag-Erling Smørgrav } else { 328b7579f77SDag-Erling Smørgrav /* if so: update data - needs a writelock */ 329b7579f77SDag-Erling Smørgrav table->space_used += need_size - 330b7579f77SDag-Erling Smørgrav (*table->sizefunc)(found->key, found->data); 331b7579f77SDag-Erling Smørgrav (*table->delkeyfunc)(entry->key, cb_arg); 332b7579f77SDag-Erling Smørgrav lru_touch(table, found); 333b7579f77SDag-Erling Smørgrav lock_rw_wrlock(&found->lock); 334b7579f77SDag-Erling Smørgrav (*table->deldatafunc)(found->data, cb_arg); 335b7579f77SDag-Erling Smørgrav found->data = data; 336b7579f77SDag-Erling Smørgrav lock_rw_unlock(&found->lock); 337b7579f77SDag-Erling Smørgrav } 338b7579f77SDag-Erling Smørgrav lock_quick_unlock(&bin->lock); 339b7579f77SDag-Erling Smørgrav if(table->space_used > table->space_max) 340b7579f77SDag-Erling Smørgrav reclaim_space(table, &reclaimlist); 341b7579f77SDag-Erling Smørgrav if(table->num >= table->size) 342b7579f77SDag-Erling Smørgrav table_grow(table); 343b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->lock); 344b7579f77SDag-Erling Smørgrav 345b7579f77SDag-Erling Smørgrav /* finish reclaim if any (outside of critical region) */ 346b7579f77SDag-Erling Smørgrav while(reclaimlist) { 347b7579f77SDag-Erling Smørgrav struct lruhash_entry* n = reclaimlist->overflow_next; 348b7579f77SDag-Erling Smørgrav void* d = reclaimlist->data; 349b7579f77SDag-Erling Smørgrav (*table->delkeyfunc)(reclaimlist->key, cb_arg); 350b7579f77SDag-Erling Smørgrav (*table->deldatafunc)(d, cb_arg); 351b7579f77SDag-Erling Smørgrav reclaimlist = n; 352b7579f77SDag-Erling Smørgrav } 353b7579f77SDag-Erling Smørgrav } 354b7579f77SDag-Erling Smørgrav 355b7579f77SDag-Erling Smørgrav struct lruhash_entry* 3563005e0a3SDag-Erling Smørgrav lruhash_lookup(struct lruhash* table, hashvalue_type hash, void* key, int wr) 357b7579f77SDag-Erling Smørgrav { 358b7579f77SDag-Erling Smørgrav struct lruhash_entry* entry; 359b7579f77SDag-Erling Smørgrav struct lruhash_bin* bin; 360b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc)); 361b7579f77SDag-Erling Smørgrav 362b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->lock); 363b7579f77SDag-Erling Smørgrav bin = &table->array[hash & table->size_mask]; 364b7579f77SDag-Erling Smørgrav lock_quick_lock(&bin->lock); 365b7579f77SDag-Erling Smørgrav if((entry=bin_find_entry(table, bin, hash, key))) 366b7579f77SDag-Erling Smørgrav lru_touch(table, entry); 367b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->lock); 368b7579f77SDag-Erling Smørgrav 369b7579f77SDag-Erling Smørgrav if(entry) { 370b7579f77SDag-Erling Smørgrav if(wr) { lock_rw_wrlock(&entry->lock); } 371b7579f77SDag-Erling Smørgrav else { lock_rw_rdlock(&entry->lock); } 372b7579f77SDag-Erling Smørgrav } 373b7579f77SDag-Erling Smørgrav lock_quick_unlock(&bin->lock); 374b7579f77SDag-Erling Smørgrav return entry; 375b7579f77SDag-Erling Smørgrav } 376b7579f77SDag-Erling Smørgrav 377b7579f77SDag-Erling Smørgrav void 3783005e0a3SDag-Erling Smørgrav lruhash_remove(struct lruhash* table, hashvalue_type hash, void* key) 379b7579f77SDag-Erling Smørgrav { 380b7579f77SDag-Erling Smørgrav struct lruhash_entry* entry; 381b7579f77SDag-Erling Smørgrav struct lruhash_bin* bin; 382b7579f77SDag-Erling Smørgrav void *d; 383b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc)); 384b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); 385b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); 386b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc)); 387b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); 388b7579f77SDag-Erling Smørgrav 389b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->lock); 390b7579f77SDag-Erling Smørgrav bin = &table->array[hash & table->size_mask]; 391b7579f77SDag-Erling Smørgrav lock_quick_lock(&bin->lock); 392b7579f77SDag-Erling Smørgrav if((entry=bin_find_entry(table, bin, hash, key))) { 393b7579f77SDag-Erling Smørgrav bin_overflow_remove(bin, entry); 394b7579f77SDag-Erling Smørgrav lru_remove(table, entry); 395b7579f77SDag-Erling Smørgrav } else { 396b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->lock); 397b7579f77SDag-Erling Smørgrav lock_quick_unlock(&bin->lock); 398b7579f77SDag-Erling Smørgrav return; 399b7579f77SDag-Erling Smørgrav } 400b7579f77SDag-Erling Smørgrav table->num--; 401b7579f77SDag-Erling Smørgrav table->space_used -= (*table->sizefunc)(entry->key, entry->data); 402b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->lock); 403b7579f77SDag-Erling Smørgrav lock_rw_wrlock(&entry->lock); 404b7579f77SDag-Erling Smørgrav if(table->markdelfunc) 405b7579f77SDag-Erling Smørgrav (*table->markdelfunc)(entry->key); 406b7579f77SDag-Erling Smørgrav lock_rw_unlock(&entry->lock); 407b7579f77SDag-Erling Smørgrav lock_quick_unlock(&bin->lock); 408b7579f77SDag-Erling Smørgrav /* finish removal */ 409b7579f77SDag-Erling Smørgrav d = entry->data; 410b7579f77SDag-Erling Smørgrav (*table->delkeyfunc)(entry->key, table->cb_arg); 411b7579f77SDag-Erling Smørgrav (*table->deldatafunc)(d, table->cb_arg); 412b7579f77SDag-Erling Smørgrav } 413b7579f77SDag-Erling Smørgrav 414b7579f77SDag-Erling Smørgrav /** clear bin, respecting locks, does not do space, LRU */ 415b7579f77SDag-Erling Smørgrav static void 416b7579f77SDag-Erling Smørgrav bin_clear(struct lruhash* table, struct lruhash_bin* bin) 417b7579f77SDag-Erling Smørgrav { 418b7579f77SDag-Erling Smørgrav struct lruhash_entry* p, *np; 419b7579f77SDag-Erling Smørgrav void *d; 420b7579f77SDag-Erling Smørgrav lock_quick_lock(&bin->lock); 421b7579f77SDag-Erling Smørgrav p = bin->overflow_list; 422b7579f77SDag-Erling Smørgrav while(p) { 423b7579f77SDag-Erling Smørgrav lock_rw_wrlock(&p->lock); 424b7579f77SDag-Erling Smørgrav np = p->overflow_next; 425b7579f77SDag-Erling Smørgrav d = p->data; 426b7579f77SDag-Erling Smørgrav if(table->markdelfunc) 427b7579f77SDag-Erling Smørgrav (*table->markdelfunc)(p->key); 428b7579f77SDag-Erling Smørgrav lock_rw_unlock(&p->lock); 429b7579f77SDag-Erling Smørgrav (*table->delkeyfunc)(p->key, table->cb_arg); 430b7579f77SDag-Erling Smørgrav (*table->deldatafunc)(d, table->cb_arg); 431b7579f77SDag-Erling Smørgrav p = np; 432b7579f77SDag-Erling Smørgrav } 433b7579f77SDag-Erling Smørgrav bin->overflow_list = NULL; 434b7579f77SDag-Erling Smørgrav lock_quick_unlock(&bin->lock); 435b7579f77SDag-Erling Smørgrav } 436b7579f77SDag-Erling Smørgrav 437b7579f77SDag-Erling Smørgrav void 438b7579f77SDag-Erling Smørgrav lruhash_clear(struct lruhash* table) 439b7579f77SDag-Erling Smørgrav { 440b7579f77SDag-Erling Smørgrav size_t i; 441b7579f77SDag-Erling Smørgrav if(!table) 442b7579f77SDag-Erling Smørgrav return; 443b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); 444b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); 445b7579f77SDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); 446b7579f77SDag-Erling Smørgrav 447b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->lock); 448b7579f77SDag-Erling Smørgrav for(i=0; i<table->size; i++) { 449b7579f77SDag-Erling Smørgrav bin_clear(table, &table->array[i]); 450b7579f77SDag-Erling Smørgrav } 451b7579f77SDag-Erling Smørgrav table->lru_start = NULL; 452b7579f77SDag-Erling Smørgrav table->lru_end = NULL; 453b7579f77SDag-Erling Smørgrav table->num = 0; 454b7579f77SDag-Erling Smørgrav table->space_used = 0; 455b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->lock); 456b7579f77SDag-Erling Smørgrav } 457b7579f77SDag-Erling Smørgrav 458b7579f77SDag-Erling Smørgrav void 459b7579f77SDag-Erling Smørgrav lruhash_status(struct lruhash* table, const char* id, int extended) 460b7579f77SDag-Erling Smørgrav { 461b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->lock); 462b7579f77SDag-Erling Smørgrav log_info("%s: %u entries, memory %u / %u", 463b7579f77SDag-Erling Smørgrav id, (unsigned)table->num, (unsigned)table->space_used, 464b7579f77SDag-Erling Smørgrav (unsigned)table->space_max); 465b7579f77SDag-Erling Smørgrav log_info(" itemsize %u, array %u, mask %d", 466b7579f77SDag-Erling Smørgrav (unsigned)(table->num? table->space_used/table->num : 0), 467b7579f77SDag-Erling Smørgrav (unsigned)table->size, table->size_mask); 468b7579f77SDag-Erling Smørgrav if(extended) { 469b7579f77SDag-Erling Smørgrav size_t i; 470b7579f77SDag-Erling Smørgrav int min=(int)table->size*2, max=-2; 471b7579f77SDag-Erling Smørgrav for(i=0; i<table->size; i++) { 472b7579f77SDag-Erling Smørgrav int here = 0; 473b7579f77SDag-Erling Smørgrav struct lruhash_entry *en; 474b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->array[i].lock); 475b7579f77SDag-Erling Smørgrav en = table->array[i].overflow_list; 476b7579f77SDag-Erling Smørgrav while(en) { 477b7579f77SDag-Erling Smørgrav here ++; 478b7579f77SDag-Erling Smørgrav en = en->overflow_next; 479b7579f77SDag-Erling Smørgrav } 480b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->array[i].lock); 481b7579f77SDag-Erling Smørgrav if(extended >= 2) 482b7579f77SDag-Erling Smørgrav log_info("bin[%d] %d", (int)i, here); 483b7579f77SDag-Erling Smørgrav if(here > max) max = here; 484b7579f77SDag-Erling Smørgrav if(here < min) min = here; 485b7579f77SDag-Erling Smørgrav } 486b7579f77SDag-Erling Smørgrav log_info(" bin min %d, avg %.2lf, max %d", min, 487b7579f77SDag-Erling Smørgrav (double)table->num/(double)table->size, max); 488b7579f77SDag-Erling Smørgrav } 489b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->lock); 490b7579f77SDag-Erling Smørgrav } 491b7579f77SDag-Erling Smørgrav 492b7579f77SDag-Erling Smørgrav size_t 493b7579f77SDag-Erling Smørgrav lruhash_get_mem(struct lruhash* table) 494b7579f77SDag-Erling Smørgrav { 495b7579f77SDag-Erling Smørgrav size_t s; 496b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->lock); 497b7579f77SDag-Erling Smørgrav s = sizeof(struct lruhash) + table->space_used; 498b7579f77SDag-Erling Smørgrav #ifdef USE_THREAD_DEBUG 499b7579f77SDag-Erling Smørgrav if(table->size != 0) { 500b7579f77SDag-Erling Smørgrav size_t i; 501b7579f77SDag-Erling Smørgrav for(i=0; i<table->size; i++) 502b7579f77SDag-Erling Smørgrav s += sizeof(struct lruhash_bin) + 503b7579f77SDag-Erling Smørgrav lock_get_mem(&table->array[i].lock); 504b7579f77SDag-Erling Smørgrav } 505b7579f77SDag-Erling Smørgrav #else /* no THREAD_DEBUG */ 506b7579f77SDag-Erling Smørgrav if(table->size != 0) 507b7579f77SDag-Erling Smørgrav s += (table->size)*(sizeof(struct lruhash_bin) + 508b7579f77SDag-Erling Smørgrav lock_get_mem(&table->array[0].lock)); 509b7579f77SDag-Erling Smørgrav #endif 510b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->lock); 511b7579f77SDag-Erling Smørgrav s += lock_get_mem(&table->lock); 512b7579f77SDag-Erling Smørgrav return s; 513b7579f77SDag-Erling Smørgrav } 514b7579f77SDag-Erling Smørgrav 515b7579f77SDag-Erling Smørgrav void 5163005e0a3SDag-Erling Smørgrav lruhash_setmarkdel(struct lruhash* table, lruhash_markdelfunc_type md) 517b7579f77SDag-Erling Smørgrav { 518b7579f77SDag-Erling Smørgrav lock_quick_lock(&table->lock); 519b7579f77SDag-Erling Smørgrav table->markdelfunc = md; 520b7579f77SDag-Erling Smørgrav lock_quick_unlock(&table->lock); 521b7579f77SDag-Erling Smørgrav } 522b7579f77SDag-Erling Smørgrav 523b7579f77SDag-Erling Smørgrav void 524b7579f77SDag-Erling Smørgrav lruhash_traverse(struct lruhash* h, int wr, 525b7579f77SDag-Erling Smørgrav void (*func)(struct lruhash_entry*, void*), void* arg) 526b7579f77SDag-Erling Smørgrav { 527b7579f77SDag-Erling Smørgrav size_t i; 528b7579f77SDag-Erling Smørgrav struct lruhash_entry* e; 529b7579f77SDag-Erling Smørgrav 530b7579f77SDag-Erling Smørgrav lock_quick_lock(&h->lock); 531b7579f77SDag-Erling Smørgrav for(i=0; i<h->size; i++) { 532b7579f77SDag-Erling Smørgrav lock_quick_lock(&h->array[i].lock); 533b7579f77SDag-Erling Smørgrav for(e = h->array[i].overflow_list; e; e = e->overflow_next) { 534b7579f77SDag-Erling Smørgrav if(wr) { 535b7579f77SDag-Erling Smørgrav lock_rw_wrlock(&e->lock); 536b7579f77SDag-Erling Smørgrav } else { 537b7579f77SDag-Erling Smørgrav lock_rw_rdlock(&e->lock); 538b7579f77SDag-Erling Smørgrav } 539b7579f77SDag-Erling Smørgrav (*func)(e, arg); 540b7579f77SDag-Erling Smørgrav lock_rw_unlock(&e->lock); 541b7579f77SDag-Erling Smørgrav } 542b7579f77SDag-Erling Smørgrav lock_quick_unlock(&h->array[i].lock); 543b7579f77SDag-Erling Smørgrav } 544b7579f77SDag-Erling Smørgrav lock_quick_unlock(&h->lock); 545b7579f77SDag-Erling Smørgrav } 546*65b390aaSDag-Erling Smørgrav 547*65b390aaSDag-Erling Smørgrav /* 548*65b390aaSDag-Erling Smørgrav * Demote: the opposite of touch, move an entry to the bottom 549*65b390aaSDag-Erling Smørgrav * of the LRU pile. 550*65b390aaSDag-Erling Smørgrav */ 551*65b390aaSDag-Erling Smørgrav 552*65b390aaSDag-Erling Smørgrav void 553*65b390aaSDag-Erling Smørgrav lru_demote(struct lruhash* table, struct lruhash_entry* entry) 554*65b390aaSDag-Erling Smørgrav { 555*65b390aaSDag-Erling Smørgrav log_assert(table && entry); 556*65b390aaSDag-Erling Smørgrav if (entry == table->lru_end) 557*65b390aaSDag-Erling Smørgrav return; /* nothing to do */ 558*65b390aaSDag-Erling Smørgrav /* remove from current lru position */ 559*65b390aaSDag-Erling Smørgrav lru_remove(table, entry); 560*65b390aaSDag-Erling Smørgrav /* add at end */ 561*65b390aaSDag-Erling Smørgrav entry->lru_next = NULL; 562*65b390aaSDag-Erling Smørgrav entry->lru_prev = table->lru_end; 563*65b390aaSDag-Erling Smørgrav 564*65b390aaSDag-Erling Smørgrav if (table->lru_end == NULL) 565*65b390aaSDag-Erling Smørgrav { 566*65b390aaSDag-Erling Smørgrav table->lru_start = entry; 567*65b390aaSDag-Erling Smørgrav } 568*65b390aaSDag-Erling Smørgrav else 569*65b390aaSDag-Erling Smørgrav { 570*65b390aaSDag-Erling Smørgrav table->lru_end->lru_next = entry; 571*65b390aaSDag-Erling Smørgrav } 572*65b390aaSDag-Erling Smørgrav table->lru_end = entry; 573*65b390aaSDag-Erling Smørgrav } 574*65b390aaSDag-Erling Smørgrav 575*65b390aaSDag-Erling Smørgrav struct lruhash_entry* 576*65b390aaSDag-Erling Smørgrav lruhash_insert_or_retrieve(struct lruhash* table, hashvalue_type hash, 577*65b390aaSDag-Erling Smørgrav struct lruhash_entry* entry, void* data, void* cb_arg) 578*65b390aaSDag-Erling Smørgrav { 579*65b390aaSDag-Erling Smørgrav struct lruhash_bin* bin; 580*65b390aaSDag-Erling Smørgrav struct lruhash_entry* found, *reclaimlist = NULL; 581*65b390aaSDag-Erling Smørgrav size_t need_size; 582*65b390aaSDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc)); 583*65b390aaSDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); 584*65b390aaSDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); 585*65b390aaSDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc)); 586*65b390aaSDag-Erling Smørgrav fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); 587*65b390aaSDag-Erling Smørgrav need_size = table->sizefunc(entry->key, data); 588*65b390aaSDag-Erling Smørgrav if (cb_arg == NULL) cb_arg = table->cb_arg; 589*65b390aaSDag-Erling Smørgrav 590*65b390aaSDag-Erling Smørgrav /* find bin */ 591*65b390aaSDag-Erling Smørgrav lock_quick_lock(&table->lock); 592*65b390aaSDag-Erling Smørgrav bin = &table->array[hash & table->size_mask]; 593*65b390aaSDag-Erling Smørgrav lock_quick_lock(&bin->lock); 594*65b390aaSDag-Erling Smørgrav 595*65b390aaSDag-Erling Smørgrav /* see if entry exists already */ 596*65b390aaSDag-Erling Smørgrav if ((found = bin_find_entry(table, bin, hash, entry->key)) != NULL) { 597*65b390aaSDag-Erling Smørgrav /* if so: keep the existing data - acquire a writelock */ 598*65b390aaSDag-Erling Smørgrav lock_rw_wrlock(&found->lock); 599*65b390aaSDag-Erling Smørgrav } 600*65b390aaSDag-Erling Smørgrav else 601*65b390aaSDag-Erling Smørgrav { 602*65b390aaSDag-Erling Smørgrav /* if not: add to bin */ 603*65b390aaSDag-Erling Smørgrav entry->overflow_next = bin->overflow_list; 604*65b390aaSDag-Erling Smørgrav bin->overflow_list = entry; 605*65b390aaSDag-Erling Smørgrav lru_front(table, entry); 606*65b390aaSDag-Erling Smørgrav table->num++; 607*65b390aaSDag-Erling Smørgrav table->space_used += need_size; 608*65b390aaSDag-Erling Smørgrav /* return the entry that was presented, and lock it */ 609*65b390aaSDag-Erling Smørgrav found = entry; 610*65b390aaSDag-Erling Smørgrav lock_rw_wrlock(&found->lock); 611*65b390aaSDag-Erling Smørgrav } 612*65b390aaSDag-Erling Smørgrav lock_quick_unlock(&bin->lock); 613*65b390aaSDag-Erling Smørgrav if (table->space_used > table->space_max) 614*65b390aaSDag-Erling Smørgrav reclaim_space(table, &reclaimlist); 615*65b390aaSDag-Erling Smørgrav if (table->num >= table->size) 616*65b390aaSDag-Erling Smørgrav table_grow(table); 617*65b390aaSDag-Erling Smørgrav lock_quick_unlock(&table->lock); 618*65b390aaSDag-Erling Smørgrav 619*65b390aaSDag-Erling Smørgrav /* finish reclaim if any (outside of critical region) */ 620*65b390aaSDag-Erling Smørgrav while (reclaimlist) { 621*65b390aaSDag-Erling Smørgrav struct lruhash_entry* n = reclaimlist->overflow_next; 622*65b390aaSDag-Erling Smørgrav void* d = reclaimlist->data; 623*65b390aaSDag-Erling Smørgrav (*table->delkeyfunc)(reclaimlist->key, cb_arg); 624*65b390aaSDag-Erling Smørgrav (*table->deldatafunc)(d, cb_arg); 625*65b390aaSDag-Erling Smørgrav reclaimlist = n; 626*65b390aaSDag-Erling Smørgrav } 627*65b390aaSDag-Erling Smørgrav 628*65b390aaSDag-Erling Smørgrav /* return the entry that was selected */ 629*65b390aaSDag-Erling Smørgrav return found; 630*65b390aaSDag-Erling Smørgrav } 631*65b390aaSDag-Erling Smørgrav 632