]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/unbound/util/storage/lruhash.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / unbound / util / storage / lruhash.c
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 LIMITED
25  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
26  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
27  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * 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
47 void
48 bin_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
61 struct lruhash* 
62 lruhash_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
96 void 
97 bin_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
115 void 
116 bin_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
151 void 
152 lruhash_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
165 void 
166 bin_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
180 void 
181 reclaim_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
216 struct lruhash_entry* 
217 bin_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
229 void 
230 table_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
264 void 
265 lru_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
275 void 
276 lru_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
286 void 
287 lru_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
298 void 
299 lruhash_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
354 struct lruhash_entry* 
355 lruhash_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
376 void 
377 lruhash_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 */
414 static void
415 bin_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
436 void
437 lruhash_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
457 void 
458 lruhash_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
491 size_t
492 lruhash_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
514 void 
515 lruhash_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
522 void 
523 lruhash_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 }