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