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