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 mask;
|
||||||
size_t used;
|
size_t used;
|
||||||
size_t seed;
|
size_t seed;
|
||||||
|
size_t dead;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MINSIZE 8
|
#define MINSIZE 8
|
||||||
@ -171,27 +172,34 @@ int htab_delete(struct htab *htab, const char* key)
|
|||||||
e->item.key = 0;
|
e->item.key = 0;
|
||||||
e->hash = 0xdeadc0de;
|
e->hash = 0xdeadc0de;
|
||||||
--htab->used;
|
--htab->used;
|
||||||
|
++htab->dead;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int htab_insert(struct htab *htab, char* key, htab_value value)
|
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);
|
struct elem *e = lookup(htab, key, hash, 0xdeadc0de);
|
||||||
if(e->item.key) {
|
if(e->item.key) {
|
||||||
/* it's not allowed to overwrite existing data */
|
/* it's not allowed to overwrite existing data */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oh = e->hash; /* save old hash in case it's tombstone marker */
|
||||||
e->item.key = key;
|
e->item.key = key;
|
||||||
e->item.data = value;
|
e->item.data = value;
|
||||||
e->hash = hash;
|
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)) {
|
if (!resize(htab, 2*htab->used)) {
|
||||||
htab->used--;
|
htab->used--;
|
||||||
e->item.key = 0;
|
e->item.key = 0;
|
||||||
|
e->hash = oh;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
htab->dead = 0;
|
||||||
|
} else if (oh == 0xdeadc0de) {
|
||||||
|
/* re-used tomb */
|
||||||
|
--htab->dead;
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user