1933707f3Ssthen /*
2933707f3Ssthen * util/storage/lruhash.c - hashtable, hash function, LRU keeping.
3933707f3Ssthen *
4933707f3Ssthen * Copyright (c) 2007, NLnet Labs. All rights reserved.
5933707f3Ssthen *
6933707f3Ssthen * This software is open source.
7933707f3Ssthen *
8933707f3Ssthen * Redistribution and use in source and binary forms, with or without
9933707f3Ssthen * modification, are permitted provided that the following conditions
10933707f3Ssthen * are met:
11933707f3Ssthen *
12933707f3Ssthen * Redistributions of source code must retain the above copyright notice,
13933707f3Ssthen * this list of conditions and the following disclaimer.
14933707f3Ssthen *
15933707f3Ssthen * Redistributions in binary form must reproduce the above copyright notice,
16933707f3Ssthen * this list of conditions and the following disclaimer in the documentation
17933707f3Ssthen * and/or other materials provided with the distribution.
18933707f3Ssthen *
19933707f3Ssthen * Neither the name of the NLNET LABS nor the names of its contributors may
20933707f3Ssthen * be used to endorse or promote products derived from this software without
21933707f3Ssthen * specific prior written permission.
22933707f3Ssthen *
23933707f3Ssthen * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
245d76a658Ssthen * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
255d76a658Ssthen * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
265d76a658Ssthen * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
275d76a658Ssthen * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
285d76a658Ssthen * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
295d76a658Ssthen * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
305d76a658Ssthen * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
315d76a658Ssthen * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
325d76a658Ssthen * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
335d76a658Ssthen * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34933707f3Ssthen */
35933707f3Ssthen
36933707f3Ssthen /**
37933707f3Ssthen * \file
38933707f3Ssthen *
39933707f3Ssthen * This file contains a hashtable with LRU keeping of entries.
40933707f3Ssthen *
41933707f3Ssthen */
42933707f3Ssthen
43933707f3Ssthen #include "config.h"
44933707f3Ssthen #include "util/storage/lruhash.h"
45933707f3Ssthen #include "util/fptr_wlist.h"
46933707f3Ssthen
47933707f3Ssthen void
bin_init(struct lruhash_bin * array,size_t size)48933707f3Ssthen bin_init(struct lruhash_bin* array, size_t size)
49933707f3Ssthen {
50933707f3Ssthen size_t i;
51933707f3Ssthen #ifdef THREADS_DISABLED
52933707f3Ssthen (void)array;
53933707f3Ssthen #endif
54933707f3Ssthen for(i=0; i<size; i++) {
55933707f3Ssthen lock_quick_init(&array[i].lock);
56933707f3Ssthen lock_protect(&array[i].lock, &array[i],
57933707f3Ssthen sizeof(struct lruhash_bin));
58933707f3Ssthen }
59933707f3Ssthen }
60933707f3Ssthen
61933707f3Ssthen struct lruhash*
lruhash_create(size_t start_size,size_t maxmem,lruhash_sizefunc_type sizefunc,lruhash_compfunc_type compfunc,lruhash_delkeyfunc_type delkeyfunc,lruhash_deldatafunc_type deldatafunc,void * arg)6277079be7Ssthen lruhash_create(size_t start_size, size_t maxmem,
6377079be7Ssthen lruhash_sizefunc_type sizefunc, lruhash_compfunc_type compfunc,
6477079be7Ssthen lruhash_delkeyfunc_type delkeyfunc,
6577079be7Ssthen lruhash_deldatafunc_type deldatafunc, void* arg)
66933707f3Ssthen {
67933707f3Ssthen struct lruhash* table = (struct lruhash*)calloc(1,
68933707f3Ssthen sizeof(struct lruhash));
69933707f3Ssthen if(!table)
70933707f3Ssthen return NULL;
71933707f3Ssthen lock_quick_init(&table->lock);
72933707f3Ssthen table->sizefunc = sizefunc;
73933707f3Ssthen table->compfunc = compfunc;
74933707f3Ssthen table->delkeyfunc = delkeyfunc;
75933707f3Ssthen table->deldatafunc = deldatafunc;
76933707f3Ssthen table->cb_arg = arg;
77933707f3Ssthen table->size = start_size;
78933707f3Ssthen table->size_mask = (int)(start_size-1);
79933707f3Ssthen table->lru_start = NULL;
80933707f3Ssthen table->lru_end = NULL;
81933707f3Ssthen table->num = 0;
82933707f3Ssthen table->space_used = 0;
83933707f3Ssthen table->space_max = maxmem;
848b7325afSsthen table->max_collisions = 0;
85933707f3Ssthen table->array = calloc(table->size, sizeof(struct lruhash_bin));
86933707f3Ssthen if(!table->array) {
87933707f3Ssthen lock_quick_destroy(&table->lock);
88933707f3Ssthen free(table);
89933707f3Ssthen return NULL;
90933707f3Ssthen }
91933707f3Ssthen bin_init(table->array, table->size);
92933707f3Ssthen lock_protect(&table->lock, table, sizeof(*table));
93933707f3Ssthen lock_protect(&table->lock, table->array,
94933707f3Ssthen table->size*sizeof(struct lruhash_bin));
95933707f3Ssthen return table;
96933707f3Ssthen }
97933707f3Ssthen
98933707f3Ssthen void
bin_delete(struct lruhash * table,struct lruhash_bin * bin)99933707f3Ssthen bin_delete(struct lruhash* table, struct lruhash_bin* bin)
100933707f3Ssthen {
101933707f3Ssthen struct lruhash_entry* p, *np;
102933707f3Ssthen void *d;
103933707f3Ssthen if(!bin)
104933707f3Ssthen return;
105933707f3Ssthen lock_quick_destroy(&bin->lock);
106933707f3Ssthen p = bin->overflow_list;
107933707f3Ssthen bin->overflow_list = NULL;
108933707f3Ssthen while(p) {
109933707f3Ssthen np = p->overflow_next;
110933707f3Ssthen d = p->data;
111933707f3Ssthen (*table->delkeyfunc)(p->key, table->cb_arg);
112933707f3Ssthen (*table->deldatafunc)(d, table->cb_arg);
113933707f3Ssthen p = np;
114933707f3Ssthen }
115933707f3Ssthen }
116933707f3Ssthen
117933707f3Ssthen void
bin_split(struct lruhash * table,struct lruhash_bin * newa,int newmask)118933707f3Ssthen bin_split(struct lruhash* table, struct lruhash_bin* newa,
119933707f3Ssthen int newmask)
120933707f3Ssthen {
121933707f3Ssthen size_t i;
122933707f3Ssthen struct lruhash_entry *p, *np;
123933707f3Ssthen struct lruhash_bin* newbin;
124933707f3Ssthen /* move entries to new table. Notice that since hash x is mapped to
125933707f3Ssthen * bin x & mask, and new mask uses one more bit, so all entries in
126933707f3Ssthen * one bin will go into the old bin or bin | newbit */
127933707f3Ssthen #ifndef THREADS_DISABLED
128933707f3Ssthen int newbit = newmask - table->size_mask;
129933707f3Ssthen #endif
130933707f3Ssthen /* so, really, this task could also be threaded, per bin. */
131933707f3Ssthen /* LRU list is not changed */
132933707f3Ssthen for(i=0; i<table->size; i++)
133933707f3Ssthen {
134933707f3Ssthen lock_quick_lock(&table->array[i].lock);
135933707f3Ssthen p = table->array[i].overflow_list;
136933707f3Ssthen /* lock both destination bins */
137933707f3Ssthen lock_quick_lock(&newa[i].lock);
138933707f3Ssthen lock_quick_lock(&newa[newbit|i].lock);
139933707f3Ssthen while(p) {
140933707f3Ssthen np = p->overflow_next;
141933707f3Ssthen /* link into correct new bin */
142933707f3Ssthen newbin = &newa[p->hash & newmask];
143933707f3Ssthen p->overflow_next = newbin->overflow_list;
144933707f3Ssthen newbin->overflow_list = p;
145933707f3Ssthen p=np;
146933707f3Ssthen }
147933707f3Ssthen lock_quick_unlock(&newa[i].lock);
148933707f3Ssthen lock_quick_unlock(&newa[newbit|i].lock);
149933707f3Ssthen lock_quick_unlock(&table->array[i].lock);
150933707f3Ssthen }
151933707f3Ssthen }
152933707f3Ssthen
153933707f3Ssthen void
lruhash_delete(struct lruhash * table)154933707f3Ssthen lruhash_delete(struct lruhash* table)
155933707f3Ssthen {
156933707f3Ssthen size_t i;
157933707f3Ssthen if(!table)
158933707f3Ssthen return;
159933707f3Ssthen /* delete lock on hashtable to force check its OK */
160933707f3Ssthen lock_quick_destroy(&table->lock);
161933707f3Ssthen for(i=0; i<table->size; i++)
162933707f3Ssthen bin_delete(table, &table->array[i]);
163933707f3Ssthen free(table->array);
164933707f3Ssthen free(table);
165933707f3Ssthen }
166933707f3Ssthen
167933707f3Ssthen void
bin_overflow_remove(struct lruhash_bin * bin,struct lruhash_entry * entry)168933707f3Ssthen bin_overflow_remove(struct lruhash_bin* bin, struct lruhash_entry* entry)
169933707f3Ssthen {
170933707f3Ssthen struct lruhash_entry* p = bin->overflow_list;
171933707f3Ssthen struct lruhash_entry** prevp = &bin->overflow_list;
172933707f3Ssthen while(p) {
173933707f3Ssthen if(p == entry) {
174933707f3Ssthen *prevp = p->overflow_next;
175933707f3Ssthen return;
176933707f3Ssthen }
177933707f3Ssthen prevp = &p->overflow_next;
178933707f3Ssthen p = p->overflow_next;
179933707f3Ssthen }
180933707f3Ssthen }
181933707f3Ssthen
182933707f3Ssthen void
reclaim_space(struct lruhash * table,struct lruhash_entry ** list)183933707f3Ssthen reclaim_space(struct lruhash* table, struct lruhash_entry** list)
184933707f3Ssthen {
185933707f3Ssthen struct lruhash_entry* d;
186933707f3Ssthen struct lruhash_bin* bin;
187933707f3Ssthen log_assert(table);
188933707f3Ssthen /* does not delete MRU entry, so table will not be empty. */
189933707f3Ssthen while(table->num > 1 && table->space_used > table->space_max) {
190933707f3Ssthen /* notice that since we hold the hashtable lock, nobody
191933707f3Ssthen can change the lru chain. So it cannot be deleted underneath
192933707f3Ssthen us. We still need the hashbin and entry write lock to make
193933707f3Ssthen sure we flush all users away from the entry.
194933707f3Ssthen which is unlikely, since it is LRU, if someone got a rdlock
195933707f3Ssthen it would be moved to front, but to be sure. */
196933707f3Ssthen d = table->lru_end;
197933707f3Ssthen /* specialised, delete from end of double linked list,
198933707f3Ssthen and we know num>1, so there is a previous lru entry. */
199933707f3Ssthen log_assert(d && d->lru_prev);
200933707f3Ssthen table->lru_end = d->lru_prev;
201933707f3Ssthen d->lru_prev->lru_next = NULL;
202933707f3Ssthen /* schedule entry for deletion */
203933707f3Ssthen bin = &table->array[d->hash & table->size_mask];
204933707f3Ssthen table->num --;
205933707f3Ssthen lock_quick_lock(&bin->lock);
206933707f3Ssthen bin_overflow_remove(bin, d);
207933707f3Ssthen d->overflow_next = *list;
208933707f3Ssthen *list = d;
209933707f3Ssthen lock_rw_wrlock(&d->lock);
210933707f3Ssthen table->space_used -= table->sizefunc(d->key, d->data);
211933707f3Ssthen if(table->markdelfunc)
212933707f3Ssthen (*table->markdelfunc)(d->key);
213933707f3Ssthen lock_rw_unlock(&d->lock);
214933707f3Ssthen lock_quick_unlock(&bin->lock);
215933707f3Ssthen }
216933707f3Ssthen }
217933707f3Ssthen
218933707f3Ssthen struct lruhash_entry*
bin_find_entry(struct lruhash * table,struct lruhash_bin * bin,hashvalue_type hash,void * key,size_t * collisions)219933707f3Ssthen bin_find_entry(struct lruhash* table,
2208b7325afSsthen struct lruhash_bin* bin, hashvalue_type hash, void* key, size_t* collisions)
221933707f3Ssthen {
2228b7325afSsthen size_t c = 0;
223933707f3Ssthen struct lruhash_entry* p = bin->overflow_list;
224933707f3Ssthen while(p) {
225933707f3Ssthen if(p->hash == hash && table->compfunc(p->key, key) == 0)
2268b7325afSsthen break;
2278b7325afSsthen c++;
228933707f3Ssthen p = p->overflow_next;
229933707f3Ssthen }
2308b7325afSsthen if (collisions != NULL)
2318b7325afSsthen *collisions = c;
2328b7325afSsthen return p;
233933707f3Ssthen }
234933707f3Ssthen
235933707f3Ssthen void
table_grow(struct lruhash * table)236933707f3Ssthen table_grow(struct lruhash* table)
237933707f3Ssthen {
238933707f3Ssthen struct lruhash_bin* newa;
239933707f3Ssthen int newmask;
240933707f3Ssthen size_t i;
241933707f3Ssthen if(table->size_mask == (int)(((size_t)-1)>>1)) {
242933707f3Ssthen log_err("hash array malloc: size_t too small");
243933707f3Ssthen return;
244933707f3Ssthen }
245933707f3Ssthen /* try to allocate new array, if not fail */
246933707f3Ssthen newa = calloc(table->size*2, sizeof(struct lruhash_bin));
247933707f3Ssthen if(!newa) {
248933707f3Ssthen log_err("hash grow: malloc failed");
249933707f3Ssthen /* continue with smaller array. Though its slower. */
250933707f3Ssthen return;
251933707f3Ssthen }
252933707f3Ssthen bin_init(newa, table->size*2);
253933707f3Ssthen newmask = (table->size_mask << 1) | 1;
254933707f3Ssthen bin_split(table, newa, newmask);
255933707f3Ssthen /* delete the old bins */
256933707f3Ssthen lock_unprotect(&table->lock, table->array);
257933707f3Ssthen for(i=0; i<table->size; i++) {
258933707f3Ssthen lock_quick_destroy(&table->array[i].lock);
259933707f3Ssthen }
260933707f3Ssthen free(table->array);
261933707f3Ssthen
262933707f3Ssthen table->size *= 2;
263933707f3Ssthen table->size_mask = newmask;
264933707f3Ssthen table->array = newa;
265933707f3Ssthen lock_protect(&table->lock, table->array,
266933707f3Ssthen table->size*sizeof(struct lruhash_bin));
267933707f3Ssthen return;
268933707f3Ssthen }
269933707f3Ssthen
270933707f3Ssthen void
lru_front(struct lruhash * table,struct lruhash_entry * entry)271933707f3Ssthen lru_front(struct lruhash* table, struct lruhash_entry* entry)
272933707f3Ssthen {
273933707f3Ssthen entry->lru_prev = NULL;
274933707f3Ssthen entry->lru_next = table->lru_start;
275933707f3Ssthen if(!table->lru_start)
276933707f3Ssthen table->lru_end = entry;
277933707f3Ssthen else table->lru_start->lru_prev = entry;
278933707f3Ssthen table->lru_start = entry;
279933707f3Ssthen }
280933707f3Ssthen
281933707f3Ssthen void
lru_remove(struct lruhash * table,struct lruhash_entry * entry)282933707f3Ssthen lru_remove(struct lruhash* table, struct lruhash_entry* entry)
283933707f3Ssthen {
284933707f3Ssthen if(entry->lru_prev)
285933707f3Ssthen entry->lru_prev->lru_next = entry->lru_next;
286933707f3Ssthen else table->lru_start = entry->lru_next;
287933707f3Ssthen if(entry->lru_next)
288933707f3Ssthen entry->lru_next->lru_prev = entry->lru_prev;
289933707f3Ssthen else table->lru_end = entry->lru_prev;
290933707f3Ssthen }
291933707f3Ssthen
292933707f3Ssthen void
lru_touch(struct lruhash * table,struct lruhash_entry * entry)293933707f3Ssthen lru_touch(struct lruhash* table, struct lruhash_entry* entry)
294933707f3Ssthen {
295933707f3Ssthen log_assert(table && entry);
296933707f3Ssthen if(entry == table->lru_start)
297933707f3Ssthen return; /* nothing to do */
298933707f3Ssthen /* remove from current lru position */
299933707f3Ssthen lru_remove(table, entry);
300933707f3Ssthen /* add at front */
301933707f3Ssthen lru_front(table, entry);
302933707f3Ssthen }
303933707f3Ssthen
304933707f3Ssthen void
lruhash_insert(struct lruhash * table,hashvalue_type hash,struct lruhash_entry * entry,void * data,void * cb_arg)30577079be7Ssthen lruhash_insert(struct lruhash* table, hashvalue_type hash,
306933707f3Ssthen struct lruhash_entry* entry, void* data, void* cb_arg)
307933707f3Ssthen {
308933707f3Ssthen struct lruhash_bin* bin;
309933707f3Ssthen struct lruhash_entry* found, *reclaimlist=NULL;
310933707f3Ssthen size_t need_size;
3118b7325afSsthen size_t collisions;
312933707f3Ssthen fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
313933707f3Ssthen fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
314933707f3Ssthen fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
315933707f3Ssthen fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc));
316933707f3Ssthen fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc));
317933707f3Ssthen need_size = table->sizefunc(entry->key, data);
318933707f3Ssthen if(cb_arg == NULL) cb_arg = table->cb_arg;
319933707f3Ssthen
320933707f3Ssthen /* find bin */
321933707f3Ssthen lock_quick_lock(&table->lock);
322933707f3Ssthen bin = &table->array[hash & table->size_mask];
323933707f3Ssthen lock_quick_lock(&bin->lock);
324933707f3Ssthen
325933707f3Ssthen /* see if entry exists already */
3268b7325afSsthen if(!(found=bin_find_entry(table, bin, hash, entry->key, &collisions))) {
327933707f3Ssthen /* if not: add to bin */
328933707f3Ssthen entry->overflow_next = bin->overflow_list;
329933707f3Ssthen bin->overflow_list = entry;
330933707f3Ssthen lru_front(table, entry);
331933707f3Ssthen table->num++;
3328b7325afSsthen if (table->max_collisions < collisions)
3338b7325afSsthen table->max_collisions = collisions;
334933707f3Ssthen table->space_used += need_size;
335933707f3Ssthen } else {
336933707f3Ssthen /* if so: update data - needs a writelock */
337933707f3Ssthen table->space_used += need_size -
338933707f3Ssthen (*table->sizefunc)(found->key, found->data);
339933707f3Ssthen (*table->delkeyfunc)(entry->key, cb_arg);
340933707f3Ssthen lru_touch(table, found);
341933707f3Ssthen lock_rw_wrlock(&found->lock);
342933707f3Ssthen (*table->deldatafunc)(found->data, cb_arg);
343933707f3Ssthen found->data = data;
344933707f3Ssthen lock_rw_unlock(&found->lock);
345933707f3Ssthen }
346933707f3Ssthen lock_quick_unlock(&bin->lock);
347933707f3Ssthen if(table->space_used > table->space_max)
348933707f3Ssthen reclaim_space(table, &reclaimlist);
349933707f3Ssthen if(table->num >= table->size)
350933707f3Ssthen table_grow(table);
351933707f3Ssthen lock_quick_unlock(&table->lock);
352933707f3Ssthen
353933707f3Ssthen /* finish reclaim if any (outside of critical region) */
354933707f3Ssthen while(reclaimlist) {
355933707f3Ssthen struct lruhash_entry* n = reclaimlist->overflow_next;
356933707f3Ssthen void* d = reclaimlist->data;
357933707f3Ssthen (*table->delkeyfunc)(reclaimlist->key, cb_arg);
358933707f3Ssthen (*table->deldatafunc)(d, cb_arg);
359933707f3Ssthen reclaimlist = n;
360933707f3Ssthen }
361933707f3Ssthen }
362933707f3Ssthen
363933707f3Ssthen struct lruhash_entry*
lruhash_lookup(struct lruhash * table,hashvalue_type hash,void * key,int wr)36477079be7Ssthen lruhash_lookup(struct lruhash* table, hashvalue_type hash, void* key, int wr)
365933707f3Ssthen {
366933707f3Ssthen struct lruhash_entry* entry;
367933707f3Ssthen struct lruhash_bin* bin;
368933707f3Ssthen fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc));
369933707f3Ssthen
370933707f3Ssthen lock_quick_lock(&table->lock);
371933707f3Ssthen bin = &table->array[hash & table->size_mask];
372933707f3Ssthen lock_quick_lock(&bin->lock);
3738b7325afSsthen if((entry=bin_find_entry(table, bin, hash, key, NULL)))
374933707f3Ssthen lru_touch(table, entry);
375933707f3Ssthen lock_quick_unlock(&table->lock);
376933707f3Ssthen
377933707f3Ssthen if(entry) {
378933707f3Ssthen if(wr) { lock_rw_wrlock(&entry->lock); }
379933707f3Ssthen else { lock_rw_rdlock(&entry->lock); }
380933707f3Ssthen }
381933707f3Ssthen lock_quick_unlock(&bin->lock);
382933707f3Ssthen return entry;
383933707f3Ssthen }
384933707f3Ssthen
385933707f3Ssthen void
lruhash_remove(struct lruhash * table,hashvalue_type hash,void * key)38677079be7Ssthen lruhash_remove(struct lruhash* table, hashvalue_type hash, void* key)
387933707f3Ssthen {
388933707f3Ssthen struct lruhash_entry* entry;
389933707f3Ssthen struct lruhash_bin* bin;
390933707f3Ssthen void *d;
391933707f3Ssthen fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
392933707f3Ssthen fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
393933707f3Ssthen fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
394933707f3Ssthen fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc));
395933707f3Ssthen fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc));
396933707f3Ssthen
397933707f3Ssthen lock_quick_lock(&table->lock);
398933707f3Ssthen bin = &table->array[hash & table->size_mask];
399933707f3Ssthen lock_quick_lock(&bin->lock);
4008b7325afSsthen if((entry=bin_find_entry(table, bin, hash, key, NULL))) {
401933707f3Ssthen bin_overflow_remove(bin, entry);
402933707f3Ssthen lru_remove(table, entry);
403933707f3Ssthen } else {
404933707f3Ssthen lock_quick_unlock(&table->lock);
405933707f3Ssthen lock_quick_unlock(&bin->lock);
406933707f3Ssthen return;
407933707f3Ssthen }
408933707f3Ssthen table->num--;
409933707f3Ssthen table->space_used -= (*table->sizefunc)(entry->key, entry->data);
410933707f3Ssthen lock_rw_wrlock(&entry->lock);
411933707f3Ssthen if(table->markdelfunc)
412933707f3Ssthen (*table->markdelfunc)(entry->key);
413933707f3Ssthen lock_rw_unlock(&entry->lock);
414933707f3Ssthen lock_quick_unlock(&bin->lock);
4159982a05dSsthen lock_quick_unlock(&table->lock);
416933707f3Ssthen /* finish removal */
417933707f3Ssthen d = entry->data;
418933707f3Ssthen (*table->delkeyfunc)(entry->key, table->cb_arg);
419933707f3Ssthen (*table->deldatafunc)(d, table->cb_arg);
420933707f3Ssthen }
421933707f3Ssthen
422933707f3Ssthen /** clear bin, respecting locks, does not do space, LRU */
423933707f3Ssthen static void
bin_clear(struct lruhash * table,struct lruhash_bin * bin)424933707f3Ssthen bin_clear(struct lruhash* table, struct lruhash_bin* bin)
425933707f3Ssthen {
426933707f3Ssthen struct lruhash_entry* p, *np;
427933707f3Ssthen void *d;
428933707f3Ssthen lock_quick_lock(&bin->lock);
429933707f3Ssthen p = bin->overflow_list;
430933707f3Ssthen while(p) {
431933707f3Ssthen lock_rw_wrlock(&p->lock);
432933707f3Ssthen np = p->overflow_next;
433933707f3Ssthen d = p->data;
434933707f3Ssthen if(table->markdelfunc)
435933707f3Ssthen (*table->markdelfunc)(p->key);
436933707f3Ssthen lock_rw_unlock(&p->lock);
437933707f3Ssthen (*table->delkeyfunc)(p->key, table->cb_arg);
438933707f3Ssthen (*table->deldatafunc)(d, table->cb_arg);
439933707f3Ssthen p = np;
440933707f3Ssthen }
441933707f3Ssthen bin->overflow_list = NULL;
442933707f3Ssthen lock_quick_unlock(&bin->lock);
443933707f3Ssthen }
444933707f3Ssthen
445933707f3Ssthen void
lruhash_clear(struct lruhash * table)446933707f3Ssthen lruhash_clear(struct lruhash* table)
447933707f3Ssthen {
448933707f3Ssthen size_t i;
449933707f3Ssthen if(!table)
450933707f3Ssthen return;
451933707f3Ssthen fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
452933707f3Ssthen fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
453933707f3Ssthen fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc));
454933707f3Ssthen
455933707f3Ssthen lock_quick_lock(&table->lock);
456933707f3Ssthen for(i=0; i<table->size; i++) {
457933707f3Ssthen bin_clear(table, &table->array[i]);
458933707f3Ssthen }
459933707f3Ssthen table->lru_start = NULL;
460933707f3Ssthen table->lru_end = NULL;
461933707f3Ssthen table->num = 0;
462933707f3Ssthen table->space_used = 0;
463933707f3Ssthen lock_quick_unlock(&table->lock);
464933707f3Ssthen }
465933707f3Ssthen
466933707f3Ssthen void
lruhash_status(struct lruhash * table,const char * id,int extended)467933707f3Ssthen lruhash_status(struct lruhash* table, const char* id, int extended)
468933707f3Ssthen {
469933707f3Ssthen lock_quick_lock(&table->lock);
470933707f3Ssthen log_info("%s: %u entries, memory %u / %u",
471933707f3Ssthen id, (unsigned)table->num, (unsigned)table->space_used,
472933707f3Ssthen (unsigned)table->space_max);
473933707f3Ssthen log_info(" itemsize %u, array %u, mask %d",
474933707f3Ssthen (unsigned)(table->num? table->space_used/table->num : 0),
475933707f3Ssthen (unsigned)table->size, table->size_mask);
476933707f3Ssthen if(extended) {
477933707f3Ssthen size_t i;
478933707f3Ssthen int min=(int)table->size*2, max=-2;
479933707f3Ssthen for(i=0; i<table->size; i++) {
480933707f3Ssthen int here = 0;
481933707f3Ssthen struct lruhash_entry *en;
482933707f3Ssthen lock_quick_lock(&table->array[i].lock);
483933707f3Ssthen en = table->array[i].overflow_list;
484933707f3Ssthen while(en) {
485933707f3Ssthen here ++;
486933707f3Ssthen en = en->overflow_next;
487933707f3Ssthen }
488933707f3Ssthen lock_quick_unlock(&table->array[i].lock);
489933707f3Ssthen if(extended >= 2)
490933707f3Ssthen log_info("bin[%d] %d", (int)i, here);
491933707f3Ssthen if(here > max) max = here;
492933707f3Ssthen if(here < min) min = here;
493933707f3Ssthen }
494933707f3Ssthen log_info(" bin min %d, avg %.2lf, max %d", min,
495933707f3Ssthen (double)table->num/(double)table->size, max);
496933707f3Ssthen }
497933707f3Ssthen lock_quick_unlock(&table->lock);
498933707f3Ssthen }
499933707f3Ssthen
500933707f3Ssthen size_t
lruhash_get_mem(struct lruhash * table)501933707f3Ssthen lruhash_get_mem(struct lruhash* table)
502933707f3Ssthen {
503933707f3Ssthen size_t s;
504933707f3Ssthen lock_quick_lock(&table->lock);
505933707f3Ssthen s = sizeof(struct lruhash) + table->space_used;
506933707f3Ssthen #ifdef USE_THREAD_DEBUG
507933707f3Ssthen if(table->size != 0) {
508933707f3Ssthen size_t i;
509933707f3Ssthen for(i=0; i<table->size; i++)
510933707f3Ssthen s += sizeof(struct lruhash_bin) +
511933707f3Ssthen lock_get_mem(&table->array[i].lock);
512933707f3Ssthen }
513933707f3Ssthen #else /* no THREAD_DEBUG */
514933707f3Ssthen if(table->size != 0)
515933707f3Ssthen s += (table->size)*(sizeof(struct lruhash_bin) +
516933707f3Ssthen lock_get_mem(&table->array[0].lock));
517933707f3Ssthen #endif
518933707f3Ssthen lock_quick_unlock(&table->lock);
519933707f3Ssthen s += lock_get_mem(&table->lock);
520933707f3Ssthen return s;
521933707f3Ssthen }
522933707f3Ssthen
523933707f3Ssthen void
lruhash_setmarkdel(struct lruhash * table,lruhash_markdelfunc_type md)52477079be7Ssthen lruhash_setmarkdel(struct lruhash* table, lruhash_markdelfunc_type md)
525933707f3Ssthen {
526933707f3Ssthen lock_quick_lock(&table->lock);
527933707f3Ssthen table->markdelfunc = md;
528933707f3Ssthen lock_quick_unlock(&table->lock);
529933707f3Ssthen }
530933707f3Ssthen
531933707f3Ssthen void
lruhash_update_space_used(struct lruhash * table,void * cb_arg,int diff_size)532*2bdc0ed1Ssthen lruhash_update_space_used(struct lruhash* table, void* cb_arg, int diff_size)
533*2bdc0ed1Ssthen {
534*2bdc0ed1Ssthen struct lruhash_entry *reclaimlist = NULL;
535*2bdc0ed1Ssthen
536*2bdc0ed1Ssthen fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
537*2bdc0ed1Ssthen fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
538*2bdc0ed1Ssthen fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
539*2bdc0ed1Ssthen fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc));
540*2bdc0ed1Ssthen
541*2bdc0ed1Ssthen if(cb_arg == NULL) cb_arg = table->cb_arg;
542*2bdc0ed1Ssthen
543*2bdc0ed1Ssthen /* update space used */
544*2bdc0ed1Ssthen lock_quick_lock(&table->lock);
545*2bdc0ed1Ssthen
546*2bdc0ed1Ssthen if((int)table->space_used + diff_size < 0)
547*2bdc0ed1Ssthen table->space_used = 0;
548*2bdc0ed1Ssthen else table->space_used = (size_t)((int)table->space_used + diff_size);
549*2bdc0ed1Ssthen
550*2bdc0ed1Ssthen if(table->space_used > table->space_max)
551*2bdc0ed1Ssthen reclaim_space(table, &reclaimlist);
552*2bdc0ed1Ssthen
553*2bdc0ed1Ssthen lock_quick_unlock(&table->lock);
554*2bdc0ed1Ssthen
555*2bdc0ed1Ssthen /* finish reclaim if any (outside of critical region) */
556*2bdc0ed1Ssthen while(reclaimlist) {
557*2bdc0ed1Ssthen struct lruhash_entry* n = reclaimlist->overflow_next;
558*2bdc0ed1Ssthen void* d = reclaimlist->data;
559*2bdc0ed1Ssthen (*table->delkeyfunc)(reclaimlist->key, cb_arg);
560*2bdc0ed1Ssthen (*table->deldatafunc)(d, cb_arg);
561*2bdc0ed1Ssthen reclaimlist = n;
562*2bdc0ed1Ssthen }
563*2bdc0ed1Ssthen }
564*2bdc0ed1Ssthen
565*2bdc0ed1Ssthen void
lruhash_traverse(struct lruhash * h,int wr,void (* func)(struct lruhash_entry *,void *),void * arg)566933707f3Ssthen lruhash_traverse(struct lruhash* h, int wr,
567933707f3Ssthen void (*func)(struct lruhash_entry*, void*), void* arg)
568933707f3Ssthen {
569933707f3Ssthen size_t i;
570933707f3Ssthen struct lruhash_entry* e;
571933707f3Ssthen
572933707f3Ssthen lock_quick_lock(&h->lock);
573933707f3Ssthen for(i=0; i<h->size; i++) {
574933707f3Ssthen lock_quick_lock(&h->array[i].lock);
575933707f3Ssthen for(e = h->array[i].overflow_list; e; e = e->overflow_next) {
576933707f3Ssthen if(wr) {
577933707f3Ssthen lock_rw_wrlock(&e->lock);
578933707f3Ssthen } else {
579933707f3Ssthen lock_rw_rdlock(&e->lock);
580933707f3Ssthen }
581933707f3Ssthen (*func)(e, arg);
582933707f3Ssthen lock_rw_unlock(&e->lock);
583933707f3Ssthen }
584933707f3Ssthen lock_quick_unlock(&h->array[i].lock);
585933707f3Ssthen }
586933707f3Ssthen lock_quick_unlock(&h->lock);
587933707f3Ssthen }
5882be9e038Ssthen
5892be9e038Ssthen /*
5902be9e038Ssthen * Demote: the opposite of touch, move an entry to the bottom
5912be9e038Ssthen * of the LRU pile.
5922be9e038Ssthen */
5932be9e038Ssthen
5942be9e038Ssthen void
lru_demote(struct lruhash * table,struct lruhash_entry * entry)5952be9e038Ssthen lru_demote(struct lruhash* table, struct lruhash_entry* entry)
5962be9e038Ssthen {
5972be9e038Ssthen log_assert(table && entry);
5982be9e038Ssthen if (entry == table->lru_end)
5992be9e038Ssthen return; /* nothing to do */
6002be9e038Ssthen /* remove from current lru position */
6012be9e038Ssthen lru_remove(table, entry);
6022be9e038Ssthen /* add at end */
6032be9e038Ssthen entry->lru_next = NULL;
6042be9e038Ssthen entry->lru_prev = table->lru_end;
6052be9e038Ssthen
6062be9e038Ssthen if (table->lru_end == NULL)
6072be9e038Ssthen {
6082be9e038Ssthen table->lru_start = entry;
6092be9e038Ssthen }
6102be9e038Ssthen else
6112be9e038Ssthen {
6122be9e038Ssthen table->lru_end->lru_next = entry;
6132be9e038Ssthen }
6142be9e038Ssthen table->lru_end = entry;
6152be9e038Ssthen }
6162be9e038Ssthen
6172be9e038Ssthen struct lruhash_entry*
lruhash_insert_or_retrieve(struct lruhash * table,hashvalue_type hash,struct lruhash_entry * entry,void * data,void * cb_arg)6182be9e038Ssthen lruhash_insert_or_retrieve(struct lruhash* table, hashvalue_type hash,
6192be9e038Ssthen struct lruhash_entry* entry, void* data, void* cb_arg)
6202be9e038Ssthen {
6212be9e038Ssthen struct lruhash_bin* bin;
6222be9e038Ssthen struct lruhash_entry* found, *reclaimlist = NULL;
6232be9e038Ssthen size_t need_size;
6248b7325afSsthen size_t collisions;
6252be9e038Ssthen fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc));
6262be9e038Ssthen fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc));
6272be9e038Ssthen fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc));
6282be9e038Ssthen fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc));
6292be9e038Ssthen fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc));
6302be9e038Ssthen need_size = table->sizefunc(entry->key, data);
6312be9e038Ssthen if (cb_arg == NULL) cb_arg = table->cb_arg;
6322be9e038Ssthen
6332be9e038Ssthen /* find bin */
6342be9e038Ssthen lock_quick_lock(&table->lock);
6352be9e038Ssthen bin = &table->array[hash & table->size_mask];
6362be9e038Ssthen lock_quick_lock(&bin->lock);
6372be9e038Ssthen
6382be9e038Ssthen /* see if entry exists already */
6398b7325afSsthen if ((found = bin_find_entry(table, bin, hash, entry->key, &collisions)) != NULL) {
6402be9e038Ssthen /* if so: keep the existing data - acquire a writelock */
6412be9e038Ssthen lock_rw_wrlock(&found->lock);
6422be9e038Ssthen }
6432be9e038Ssthen else
6442be9e038Ssthen {
6452be9e038Ssthen /* if not: add to bin */
6462be9e038Ssthen entry->overflow_next = bin->overflow_list;
6472be9e038Ssthen bin->overflow_list = entry;
6482be9e038Ssthen lru_front(table, entry);
6492be9e038Ssthen table->num++;
6508b7325afSsthen if (table->max_collisions < collisions)
6518b7325afSsthen table->max_collisions = collisions;
6522be9e038Ssthen table->space_used += need_size;
6532be9e038Ssthen /* return the entry that was presented, and lock it */
6542be9e038Ssthen found = entry;
6552be9e038Ssthen lock_rw_wrlock(&found->lock);
6562be9e038Ssthen }
6572be9e038Ssthen lock_quick_unlock(&bin->lock);
6582be9e038Ssthen if (table->space_used > table->space_max)
6592be9e038Ssthen reclaim_space(table, &reclaimlist);
6602be9e038Ssthen if (table->num >= table->size)
6612be9e038Ssthen table_grow(table);
6622be9e038Ssthen lock_quick_unlock(&table->lock);
6632be9e038Ssthen
6642be9e038Ssthen /* finish reclaim if any (outside of critical region) */
6652be9e038Ssthen while (reclaimlist) {
6662be9e038Ssthen struct lruhash_entry* n = reclaimlist->overflow_next;
6672be9e038Ssthen void* d = reclaimlist->data;
6682be9e038Ssthen (*table->delkeyfunc)(reclaimlist->key, cb_arg);
6692be9e038Ssthen (*table->deldatafunc)(d, cb_arg);
6702be9e038Ssthen reclaimlist = n;
6712be9e038Ssthen }
6722be9e038Ssthen
6732be9e038Ssthen /* return the entry that was selected */
6742be9e038Ssthen return found;
6752be9e038Ssthen }
6762be9e038Ssthen
677