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 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, 63 lruhash_sizefunc_type sizefunc, lruhash_compfunc_type compfunc, 64 lruhash_delkeyfunc_type delkeyfunc, 65 lruhash_deldatafunc_type deldatafunc, void* arg) 66 { 67 struct lruhash* table = (struct lruhash*)calloc(1, 68 sizeof(struct lruhash)); 69 if(!table) 70 return NULL; 71 lock_quick_init(&table->lock); 72 table->sizefunc = sizefunc; 73 table->compfunc = compfunc; 74 table->delkeyfunc = delkeyfunc; 75 table->deldatafunc = deldatafunc; 76 table->cb_arg = arg; 77 table->size = start_size; 78 table->size_mask = (int)(start_size-1); 79 table->lru_start = NULL; 80 table->lru_end = NULL; 81 table->num = 0; 82 table->space_used = 0; 83 table->space_max = maxmem; 84 table->array = calloc(table->size, sizeof(struct lruhash_bin)); 85 if(!table->array) { 86 lock_quick_destroy(&table->lock); 87 free(table); 88 return NULL; 89 } 90 bin_init(table->array, table->size); 91 lock_protect(&table->lock, table, sizeof(*table)); 92 lock_protect(&table->lock, table->array, 93 table->size*sizeof(struct lruhash_bin)); 94 return table; 95 } 96 97 void 98 bin_delete(struct lruhash* table, struct lruhash_bin* bin) 99 { 100 struct lruhash_entry* p, *np; 101 void *d; 102 if(!bin) 103 return; 104 lock_quick_destroy(&bin->lock); 105 p = bin->overflow_list; 106 bin->overflow_list = NULL; 107 while(p) { 108 np = p->overflow_next; 109 d = p->data; 110 (*table->delkeyfunc)(p->key, table->cb_arg); 111 (*table->deldatafunc)(d, table->cb_arg); 112 p = np; 113 } 114 } 115 116 void 117 bin_split(struct lruhash* table, struct lruhash_bin* newa, 118 int newmask) 119 { 120 size_t i; 121 struct lruhash_entry *p, *np; 122 struct lruhash_bin* newbin; 123 /* move entries to new table. Notice that since hash x is mapped to 124 * bin x & mask, and new mask uses one more bit, so all entries in 125 * one bin will go into the old bin or bin | newbit */ 126 #ifndef THREADS_DISABLED 127 int newbit = newmask - table->size_mask; 128 #endif 129 /* so, really, this task could also be threaded, per bin. */ 130 /* LRU list is not changed */ 131 for(i=0; i<table->size; i++) 132 { 133 lock_quick_lock(&table->array[i].lock); 134 p = table->array[i].overflow_list; 135 /* lock both destination bins */ 136 lock_quick_lock(&newa[i].lock); 137 lock_quick_lock(&newa[newbit|i].lock); 138 while(p) { 139 np = p->overflow_next; 140 /* link into correct new bin */ 141 newbin = &newa[p->hash & newmask]; 142 p->overflow_next = newbin->overflow_list; 143 newbin->overflow_list = p; 144 p=np; 145 } 146 lock_quick_unlock(&newa[i].lock); 147 lock_quick_unlock(&newa[newbit|i].lock); 148 lock_quick_unlock(&table->array[i].lock); 149 } 150 } 151 152 void 153 lruhash_delete(struct lruhash* table) 154 { 155 size_t i; 156 if(!table) 157 return; 158 /* delete lock on hashtable to force check its OK */ 159 lock_quick_destroy(&table->lock); 160 for(i=0; i<table->size; i++) 161 bin_delete(table, &table->array[i]); 162 free(table->array); 163 free(table); 164 } 165 166 void 167 bin_overflow_remove(struct lruhash_bin* bin, struct lruhash_entry* entry) 168 { 169 struct lruhash_entry* p = bin->overflow_list; 170 struct lruhash_entry** prevp = &bin->overflow_list; 171 while(p) { 172 if(p == entry) { 173 *prevp = p->overflow_next; 174 return; 175 } 176 prevp = &p->overflow_next; 177 p = p->overflow_next; 178 } 179 } 180 181 void 182 reclaim_space(struct lruhash* table, struct lruhash_entry** list) 183 { 184 struct lruhash_entry* d; 185 struct lruhash_bin* bin; 186 log_assert(table); 187 /* does not delete MRU entry, so table will not be empty. */ 188 while(table->num > 1 && table->space_used > table->space_max) { 189 /* notice that since we hold the hashtable lock, nobody 190 can change the lru chain. So it cannot be deleted underneath 191 us. We still need the hashbin and entry write lock to make 192 sure we flush all users away from the entry. 193 which is unlikely, since it is LRU, if someone got a rdlock 194 it would be moved to front, but to be sure. */ 195 d = table->lru_end; 196 /* specialised, delete from end of double linked list, 197 and we know num>1, so there is a previous lru entry. */ 198 log_assert(d && d->lru_prev); 199 table->lru_end = d->lru_prev; 200 d->lru_prev->lru_next = NULL; 201 /* schedule entry for deletion */ 202 bin = &table->array[d->hash & table->size_mask]; 203 table->num --; 204 lock_quick_lock(&bin->lock); 205 bin_overflow_remove(bin, d); 206 d->overflow_next = *list; 207 *list = d; 208 lock_rw_wrlock(&d->lock); 209 table->space_used -= table->sizefunc(d->key, d->data); 210 if(table->markdelfunc) 211 (*table->markdelfunc)(d->key); 212 lock_rw_unlock(&d->lock); 213 lock_quick_unlock(&bin->lock); 214 } 215 } 216 217 struct lruhash_entry* 218 bin_find_entry(struct lruhash* table, 219 struct lruhash_bin* bin, hashvalue_type hash, void* key) 220 { 221 struct lruhash_entry* p = bin->overflow_list; 222 while(p) { 223 if(p->hash == hash && table->compfunc(p->key, key) == 0) 224 return p; 225 p = p->overflow_next; 226 } 227 return NULL; 228 } 229 230 void 231 table_grow(struct lruhash* table) 232 { 233 struct lruhash_bin* newa; 234 int newmask; 235 size_t i; 236 if(table->size_mask == (int)(((size_t)-1)>>1)) { 237 log_err("hash array malloc: size_t too small"); 238 return; 239 } 240 /* try to allocate new array, if not fail */ 241 newa = calloc(table->size*2, sizeof(struct lruhash_bin)); 242 if(!newa) { 243 log_err("hash grow: malloc failed"); 244 /* continue with smaller array. Though its slower. */ 245 return; 246 } 247 bin_init(newa, table->size*2); 248 newmask = (table->size_mask << 1) | 1; 249 bin_split(table, newa, newmask); 250 /* delete the old bins */ 251 lock_unprotect(&table->lock, table->array); 252 for(i=0; i<table->size; i++) { 253 lock_quick_destroy(&table->array[i].lock); 254 } 255 free(table->array); 256 257 table->size *= 2; 258 table->size_mask = newmask; 259 table->array = newa; 260 lock_protect(&table->lock, table->array, 261 table->size*sizeof(struct lruhash_bin)); 262 return; 263 } 264 265 void 266 lru_front(struct lruhash* table, struct lruhash_entry* entry) 267 { 268 entry->lru_prev = NULL; 269 entry->lru_next = table->lru_start; 270 if(!table->lru_start) 271 table->lru_end = entry; 272 else table->lru_start->lru_prev = entry; 273 table->lru_start = entry; 274 } 275 276 void 277 lru_remove(struct lruhash* table, struct lruhash_entry* entry) 278 { 279 if(entry->lru_prev) 280 entry->lru_prev->lru_next = entry->lru_next; 281 else table->lru_start = entry->lru_next; 282 if(entry->lru_next) 283 entry->lru_next->lru_prev = entry->lru_prev; 284 else table->lru_end = entry->lru_prev; 285 } 286 287 void 288 lru_touch(struct lruhash* table, struct lruhash_entry* entry) 289 { 290 log_assert(table && entry); 291 if(entry == table->lru_start) 292 return; /* nothing to do */ 293 /* remove from current lru position */ 294 lru_remove(table, entry); 295 /* add at front */ 296 lru_front(table, entry); 297 } 298 299 void 300 lruhash_insert(struct lruhash* table, hashvalue_type hash, 301 struct lruhash_entry* entry, void* data, void* cb_arg) 302 { 303 struct lruhash_bin* bin; 304 struct lruhash_entry* found, *reclaimlist=NULL; 305 size_t need_size; 306 fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc)); 307 fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); 308 fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); 309 fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc)); 310 fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); 311 need_size = table->sizefunc(entry->key, data); 312 if(cb_arg == NULL) cb_arg = table->cb_arg; 313 314 /* find bin */ 315 lock_quick_lock(&table->lock); 316 bin = &table->array[hash & table->size_mask]; 317 lock_quick_lock(&bin->lock); 318 319 /* see if entry exists already */ 320 if(!(found=bin_find_entry(table, bin, hash, entry->key))) { 321 /* if not: add to bin */ 322 entry->overflow_next = bin->overflow_list; 323 bin->overflow_list = entry; 324 lru_front(table, entry); 325 table->num++; 326 table->space_used += need_size; 327 } else { 328 /* if so: update data - needs a writelock */ 329 table->space_used += need_size - 330 (*table->sizefunc)(found->key, found->data); 331 (*table->delkeyfunc)(entry->key, cb_arg); 332 lru_touch(table, found); 333 lock_rw_wrlock(&found->lock); 334 (*table->deldatafunc)(found->data, cb_arg); 335 found->data = data; 336 lock_rw_unlock(&found->lock); 337 } 338 lock_quick_unlock(&bin->lock); 339 if(table->space_used > table->space_max) 340 reclaim_space(table, &reclaimlist); 341 if(table->num >= table->size) 342 table_grow(table); 343 lock_quick_unlock(&table->lock); 344 345 /* finish reclaim if any (outside of critical region) */ 346 while(reclaimlist) { 347 struct lruhash_entry* n = reclaimlist->overflow_next; 348 void* d = reclaimlist->data; 349 (*table->delkeyfunc)(reclaimlist->key, cb_arg); 350 (*table->deldatafunc)(d, cb_arg); 351 reclaimlist = n; 352 } 353 } 354 355 struct lruhash_entry* 356 lruhash_lookup(struct lruhash* table, hashvalue_type hash, void* key, int wr) 357 { 358 struct lruhash_entry* entry; 359 struct lruhash_bin* bin; 360 fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc)); 361 362 lock_quick_lock(&table->lock); 363 bin = &table->array[hash & table->size_mask]; 364 lock_quick_lock(&bin->lock); 365 if((entry=bin_find_entry(table, bin, hash, key))) 366 lru_touch(table, entry); 367 lock_quick_unlock(&table->lock); 368 369 if(entry) { 370 if(wr) { lock_rw_wrlock(&entry->lock); } 371 else { lock_rw_rdlock(&entry->lock); } 372 } 373 lock_quick_unlock(&bin->lock); 374 return entry; 375 } 376 377 void 378 lruhash_remove(struct lruhash* table, hashvalue_type hash, void* key) 379 { 380 struct lruhash_entry* entry; 381 struct lruhash_bin* bin; 382 void *d; 383 fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc)); 384 fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); 385 fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); 386 fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc)); 387 fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); 388 389 lock_quick_lock(&table->lock); 390 bin = &table->array[hash & table->size_mask]; 391 lock_quick_lock(&bin->lock); 392 if((entry=bin_find_entry(table, bin, hash, key))) { 393 bin_overflow_remove(bin, entry); 394 lru_remove(table, entry); 395 } else { 396 lock_quick_unlock(&table->lock); 397 lock_quick_unlock(&bin->lock); 398 return; 399 } 400 table->num--; 401 table->space_used -= (*table->sizefunc)(entry->key, entry->data); 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 lock_quick_unlock(&table->lock); 408 /* finish removal */ 409 d = entry->data; 410 (*table->delkeyfunc)(entry->key, table->cb_arg); 411 (*table->deldatafunc)(d, table->cb_arg); 412 } 413 414 /** clear bin, respecting locks, does not do space, LRU */ 415 static void 416 bin_clear(struct lruhash* table, struct lruhash_bin* bin) 417 { 418 struct lruhash_entry* p, *np; 419 void *d; 420 lock_quick_lock(&bin->lock); 421 p = bin->overflow_list; 422 while(p) { 423 lock_rw_wrlock(&p->lock); 424 np = p->overflow_next; 425 d = p->data; 426 if(table->markdelfunc) 427 (*table->markdelfunc)(p->key); 428 lock_rw_unlock(&p->lock); 429 (*table->delkeyfunc)(p->key, table->cb_arg); 430 (*table->deldatafunc)(d, table->cb_arg); 431 p = np; 432 } 433 bin->overflow_list = NULL; 434 lock_quick_unlock(&bin->lock); 435 } 436 437 void 438 lruhash_clear(struct lruhash* table) 439 { 440 size_t i; 441 if(!table) 442 return; 443 fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); 444 fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); 445 fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); 446 447 lock_quick_lock(&table->lock); 448 for(i=0; i<table->size; i++) { 449 bin_clear(table, &table->array[i]); 450 } 451 table->lru_start = NULL; 452 table->lru_end = NULL; 453 table->num = 0; 454 table->space_used = 0; 455 lock_quick_unlock(&table->lock); 456 } 457 458 void 459 lruhash_status(struct lruhash* table, const char* id, int extended) 460 { 461 lock_quick_lock(&table->lock); 462 log_info("%s: %u entries, memory %u / %u", 463 id, (unsigned)table->num, (unsigned)table->space_used, 464 (unsigned)table->space_max); 465 log_info(" itemsize %u, array %u, mask %d", 466 (unsigned)(table->num? table->space_used/table->num : 0), 467 (unsigned)table->size, table->size_mask); 468 if(extended) { 469 size_t i; 470 int min=(int)table->size*2, max=-2; 471 for(i=0; i<table->size; i++) { 472 int here = 0; 473 struct lruhash_entry *en; 474 lock_quick_lock(&table->array[i].lock); 475 en = table->array[i].overflow_list; 476 while(en) { 477 here ++; 478 en = en->overflow_next; 479 } 480 lock_quick_unlock(&table->array[i].lock); 481 if(extended >= 2) 482 log_info("bin[%d] %d", (int)i, here); 483 if(here > max) max = here; 484 if(here < min) min = here; 485 } 486 log_info(" bin min %d, avg %.2lf, max %d", min, 487 (double)table->num/(double)table->size, max); 488 } 489 lock_quick_unlock(&table->lock); 490 } 491 492 size_t 493 lruhash_get_mem(struct lruhash* table) 494 { 495 size_t s; 496 lock_quick_lock(&table->lock); 497 s = sizeof(struct lruhash) + table->space_used; 498 #ifdef USE_THREAD_DEBUG 499 if(table->size != 0) { 500 size_t i; 501 for(i=0; i<table->size; i++) 502 s += sizeof(struct lruhash_bin) + 503 lock_get_mem(&table->array[i].lock); 504 } 505 #else /* no THREAD_DEBUG */ 506 if(table->size != 0) 507 s += (table->size)*(sizeof(struct lruhash_bin) + 508 lock_get_mem(&table->array[0].lock)); 509 #endif 510 lock_quick_unlock(&table->lock); 511 s += lock_get_mem(&table->lock); 512 return s; 513 } 514 515 void 516 lruhash_setmarkdel(struct lruhash* table, lruhash_markdelfunc_type md) 517 { 518 lock_quick_lock(&table->lock); 519 table->markdelfunc = md; 520 lock_quick_unlock(&table->lock); 521 } 522 523 void 524 lruhash_traverse(struct lruhash* h, int wr, 525 void (*func)(struct lruhash_entry*, void*), void* arg) 526 { 527 size_t i; 528 struct lruhash_entry* e; 529 530 lock_quick_lock(&h->lock); 531 for(i=0; i<h->size; i++) { 532 lock_quick_lock(&h->array[i].lock); 533 for(e = h->array[i].overflow_list; e; e = e->overflow_next) { 534 if(wr) { 535 lock_rw_wrlock(&e->lock); 536 } else { 537 lock_rw_rdlock(&e->lock); 538 } 539 (*func)(e, arg); 540 lock_rw_unlock(&e->lock); 541 } 542 lock_quick_unlock(&h->array[i].lock); 543 } 544 lock_quick_unlock(&h->lock); 545 } 546 547 /* 548 * Demote: the opposite of touch, move an entry to the bottom 549 * of the LRU pile. 550 */ 551 552 void 553 lru_demote(struct lruhash* table, struct lruhash_entry* entry) 554 { 555 log_assert(table && entry); 556 if (entry == table->lru_end) 557 return; /* nothing to do */ 558 /* remove from current lru position */ 559 lru_remove(table, entry); 560 /* add at end */ 561 entry->lru_next = NULL; 562 entry->lru_prev = table->lru_end; 563 564 if (table->lru_end == NULL) 565 { 566 table->lru_start = entry; 567 } 568 else 569 { 570 table->lru_end->lru_next = entry; 571 } 572 table->lru_end = entry; 573 } 574 575 struct lruhash_entry* 576 lruhash_insert_or_retrieve(struct lruhash* table, hashvalue_type hash, 577 struct lruhash_entry* entry, void* data, void* cb_arg) 578 { 579 struct lruhash_bin* bin; 580 struct lruhash_entry* found, *reclaimlist = NULL; 581 size_t need_size; 582 fptr_ok(fptr_whitelist_hash_sizefunc(table->sizefunc)); 583 fptr_ok(fptr_whitelist_hash_delkeyfunc(table->delkeyfunc)); 584 fptr_ok(fptr_whitelist_hash_deldatafunc(table->deldatafunc)); 585 fptr_ok(fptr_whitelist_hash_compfunc(table->compfunc)); 586 fptr_ok(fptr_whitelist_hash_markdelfunc(table->markdelfunc)); 587 need_size = table->sizefunc(entry->key, data); 588 if (cb_arg == NULL) cb_arg = table->cb_arg; 589 590 /* find bin */ 591 lock_quick_lock(&table->lock); 592 bin = &table->array[hash & table->size_mask]; 593 lock_quick_lock(&bin->lock); 594 595 /* see if entry exists already */ 596 if ((found = bin_find_entry(table, bin, hash, entry->key)) != NULL) { 597 /* if so: keep the existing data - acquire a writelock */ 598 lock_rw_wrlock(&found->lock); 599 } 600 else 601 { 602 /* if not: add to bin */ 603 entry->overflow_next = bin->overflow_list; 604 bin->overflow_list = entry; 605 lru_front(table, entry); 606 table->num++; 607 table->space_used += need_size; 608 /* return the entry that was presented, and lock it */ 609 found = entry; 610 lock_rw_wrlock(&found->lock); 611 } 612 lock_quick_unlock(&bin->lock); 613 if (table->space_used > table->space_max) 614 reclaim_space(table, &reclaimlist); 615 if (table->num >= table->size) 616 table_grow(table); 617 lock_quick_unlock(&table->lock); 618 619 /* finish reclaim if any (outside of critical region) */ 620 while (reclaimlist) { 621 struct lruhash_entry* n = reclaimlist->overflow_next; 622 void* d = reclaimlist->data; 623 (*table->delkeyfunc)(reclaimlist->key, cb_arg); 624 (*table->deldatafunc)(d, cb_arg); 625 reclaimlist = n; 626 } 627 628 /* return the entry that was selected */ 629 return found; 630 } 631 632