htab: prevent filling up of table with tombstones
as pointed out by @craigbarnes [0], using the latest fix for the tombstone issue, it's possible to provoke a situation that causes an endless loop when all free slots in the table are filled up with tombstones and htab_find() is called. therefore we need to account for those as well when deciding if there's a need to call resize() so there's never more than 75% of the table used by either dead or live items. the resize() serves as a rehash which gets rid of all deleted entries, and it might cause the table size to shrink if htab_insert() is called after a lot of items have been removed. [0]: https://github.com/rofl0r/htab/issues/1#issuecomment-800094442 testcase: #include <assert.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include "hsearch.h" #define HTAB_OOM_TEST #include "hsearch.c" static char *xstrdup(const char *str) { char *dup = strdup(str); assert(dup); return dup; } void utoa(unsigned number, char* buffer) { int lentest, len = 0, i, start = 0; lentest = number; do { len++; lentest /= 10; } while(lentest); buffer[start+len] = 0; do { i = number % 10; buffer[start+len - 1] = '0' + i; number -= i; len -= 1; number /= 10; } while (number); } #define TESTSIZE 8 #define KEEP 1 static char* notorious[TESTSIZE]; static void prep() { srand(0); char buf[16]; size_t filled = 0; while(filled < TESTSIZE) { utoa(rand(), buf); size_t idx = keyhash(buf) & (TESTSIZE-1); if(!notorious[idx]) { notorious[idx] = xstrdup(buf); ++filled; } } } int main(void) { struct htab *h = htab_create(TESTSIZE); size_t i; assert(h); prep(); for(i=0; i<TESTSIZE; ++i) { char *key = notorious[i]; printf("[%zu] = \"%s\"\n", i, key); int r = htab_insert(h, key, HTV_N(42)); if(!r == 1) { printf("element %zu couldn't be inserted\n", i); break; } assert(r == 1); // Ensure newly inserted entry can be found assert(htab_find(h, key)); if(i >= KEEP) htab_delete(h, key); } htab_find(h, "looooop"); return 0; }
This commit is contained in:
parent
48860bbe26
commit
64badd6b37
@ -49,6 +49,7 @@ struct htab {
|
||||
size_t mask;
|
||||
size_t used;
|
||||
size_t seed;
|
||||
size_t dead;
|
||||
};
|
||||
|
||||
#define MINSIZE 8
|
||||
@ -171,27 +172,34 @@ int htab_delete(struct htab *htab, const char* key)
|
||||
e->item.key = 0;
|
||||
e->hash = 0xdeadc0de;
|
||||
--htab->used;
|
||||
++htab->dead;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int htab_insert(struct htab *htab, char* key, htab_value value)
|
||||
{
|
||||
size_t hash = keyhash(key, htab->seed);
|
||||
size_t hash = keyhash(key, htab->seed), oh;
|
||||
struct elem *e = lookup(htab, key, hash, 0xdeadc0de);
|
||||
if(e->item.key) {
|
||||
/* it's not allowed to overwrite existing data */
|
||||
return 0;
|
||||
}
|
||||
|
||||
oh = e->hash; /* save old hash in case it's tombstone marker */
|
||||
e->item.key = key;
|
||||
e->item.data = value;
|
||||
e->hash = hash;
|
||||
if (++htab->used > htab->mask - htab->mask/4) {
|
||||
if (++htab->used + htab->dead > htab->mask - htab->mask/4) {
|
||||
if (!resize(htab, 2*htab->used)) {
|
||||
htab->used--;
|
||||
e->item.key = 0;
|
||||
e->hash = oh;
|
||||
return 0;
|
||||
}
|
||||
htab->dead = 0;
|
||||
} else if (oh == 0xdeadc0de) {
|
||||
/* re-used tomb */
|
||||
--htab->dead;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user