xref: /openbsd-src/usr.sbin/unbound/util/storage/lruhash.c (revision 2bdc0ed15d5bcb82703283d48e5e85ed47cc34d3)
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