SDL_HashTable is now optionally thread-safe

Fixes https://github.com/libsdl-org/SDL/issues/11635
This commit is contained in:
Sam Lantinga
2024-12-12 11:51:27 -08:00
parent e0cee83a3a
commit 61511c48a4
12 changed files with 175 additions and 181 deletions

View File

@@ -44,6 +44,7 @@ SDL_COMPILE_TIME_ASSERT(sizeof_SDL_HashItem, sizeof(SDL_HashItem) <= MAX_HASHITE
struct SDL_HashTable
{
SDL_RWLock *lock;
SDL_HashItem *table;
SDL_HashTable_HashFn hash;
SDL_HashTable_KeyMatchFn keymatch;
@@ -60,6 +61,7 @@ SDL_HashTable *SDL_CreateHashTable(void *data,
SDL_HashTable_HashFn hashfn,
SDL_HashTable_KeyMatchFn keymatchfn,
SDL_HashTable_NukeFn nukefn,
bool threadsafe,
bool stackable)
{
SDL_HashTable *table;
@@ -80,9 +82,14 @@ SDL_HashTable *SDL_CreateHashTable(void *data,
return NULL;
}
if (threadsafe) {
// Don't fail if we can't create a lock (single threaded environment?)
table->lock = SDL_CreateRWLock();
}
table->table = (SDL_HashItem *)SDL_calloc(num_buckets, sizeof(SDL_HashItem));
if (!table->table) {
SDL_free(table);
SDL_DestroyHashTable(table);
return NULL;
}
@@ -289,17 +296,22 @@ bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *
{
SDL_HashItem *item;
Uint32 hash;
bool result = false;
if (!table) {
return false;
}
if (table->lock) {
SDL_LockRWLockForWriting(table->lock);
}
hash = calc_hash(table, key);
item = find_first_item(table, key, hash);
if (item && !table->stackable) {
// TODO: Maybe allow overwrites? We could do it more efficiently here than unset followed by insert.
return false;
// Allow overwrites, this might have been inserted on another thread
delete_item(table, item);
}
SDL_HashItem new_item;
@@ -313,18 +325,25 @@ bool SDL_InsertIntoHashTable(SDL_HashTable *table, const void *key, const void *
if (!maybe_resize(table)) {
table->num_occupied_slots--;
return false;
goto done;
}
// This never returns NULL
insert_item(&new_item, table->table, table->hash_mask, &table->max_probe_len);
return true;
result = true;
done:
if (table->lock) {
SDL_UnlockRWLock(table->lock);
}
return result;
}
bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void **value)
{
Uint32 hash;
SDL_HashItem *i;
bool result = false;
if (!table) {
if (value) {
@@ -333,26 +352,39 @@ bool SDL_FindInHashTable(const SDL_HashTable *table, const void *key, const void
return false;
}
if (table->lock) {
SDL_LockRWLockForReading(table->lock);
}
hash = calc_hash(table, key);
i = find_first_item(table, key, hash);
if (i) {
if (value) {
*value = i->value;
}
return true;
result = true;
}
return false;
if (table->lock) {
SDL_UnlockRWLock(table->lock);
}
return result;
}
bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key)
{
Uint32 hash;
SDL_HashItem *item;
bool result = false;
if (!table) {
return false;
}
if (table->lock) {
SDL_LockRWLockForWriting(table->lock);
}
// FIXME: what to do for stacking hashtables?
// The original code removes just one item.
// This hashtable happens to preserve the insertion order of multi-value keys,
@@ -361,13 +393,18 @@ bool SDL_RemoveFromHashTable(SDL_HashTable *table, const void *key)
hash = calc_hash(table, key);
item = find_first_item(table, key, hash);
if (!item) {
return false;
goto done;
}
delete_item(table, item);
return true;
result = true;
done:
if (table->lock) {
SDL_UnlockRWLock(table->lock);
}
return result;
}
bool SDL_IterateHashTableKey(const SDL_HashTable *table, const void *key, const void **_value, void **iter)
@@ -456,6 +493,28 @@ bool SDL_HashTableEmpty(SDL_HashTable *table)
return !(table && table->num_occupied_slots);
}
void SDL_LockHashTable(SDL_HashTable *table, bool for_writing)
{
if (!table) {
return;
}
if (for_writing) {
SDL_LockRWLockForWriting(table->lock);
} else {
SDL_LockRWLockForReading(table->lock);
}
}
void SDL_UnlockHashTable(SDL_HashTable *table)
{
if (!table) {
return;
}
SDL_UnlockRWLock(table->lock);
}
static void nuke_all(SDL_HashTable *table)
{
void *data = table->data;
@@ -472,22 +531,25 @@ static void nuke_all(SDL_HashTable *table)
void SDL_EmptyHashTable(SDL_HashTable *table)
{
if (table) {
if (table->nuke) {
nuke_all(table);
}
SDL_LockRWLockForWriting(table->lock);
{
if (table->nuke) {
nuke_all(table);
}
SDL_memset(table->table, 0, sizeof(*table->table) * (table->hash_mask + 1));
table->num_occupied_slots = 0;
SDL_memset(table->table, 0, sizeof(*table->table) * (table->hash_mask + 1));
table->num_occupied_slots = 0;
}
SDL_UnlockRWLock(table->lock);
}
}
void SDL_DestroyHashTable(SDL_HashTable *table)
{
if (table) {
if (table->nuke) {
nuke_all(table);
}
SDL_EmptyHashTable(table);
SDL_DestroyRWLock(table->lock);
SDL_free(table->table);
SDL_free(table);
}