From 279f261aedaa9c1765b650c65967a81b82212e27 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Mon, 24 Apr 2023 16:19:17 +0000 Subject: [PATCH] Fix race conditions that are possible in Db. This is accomplished by locking the entire database, and keeping it locked until the last reference is unlocked. We get rid of per-reference locks, because those are what cause race conditions. --- src/Db.c | 65 ++++++++++++++++++++++++-------------------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/src/Db.c b/src/Db.c index e628d00..0def0d5 100644 --- a/src/Db.c +++ b/src/Db.c @@ -75,7 +75,6 @@ struct Db struct DbRef { HashMap *json; - pthread_mutex_t lock; unsigned long ts; size_t size; @@ -290,16 +289,7 @@ DbCacheEvict(Db * db) { char *hash; - if (pthread_mutex_trylock(&ref->lock) != 0) - { - /* This ref is locked by another thread, don't evict it. */ - ref = ref->next; - continue; - } - JsonFree(ref->json); - pthread_mutex_unlock(&ref->lock); - pthread_mutex_destroy(&ref->lock); hash = DbHashKey(ref->name); HashMapDelete(db->cache, hash); @@ -338,6 +328,7 @@ Db * DbOpen(char *dir, size_t cache) { Db *db; + pthread_mutexattr_t attr; if (!dir) { @@ -353,7 +344,10 @@ DbOpen(char *dir, size_t cache) db->dir = dir; db->maxCache = cache; - pthread_mutex_init(&db->lock, NULL); + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init(&db->lock, &attr); + pthread_mutexattr_destroy(&attr); db->mostRecent = NULL; db->leastRecent = NULL; @@ -383,6 +377,8 @@ DbMaxCacheSet(Db * db, size_t cache) return; } + pthread_mutex_lock(&db->lock); + db->maxCache = cache; if (db->maxCache && !db->cache) { @@ -391,6 +387,8 @@ DbMaxCacheSet(Db * db, size_t cache) } DbCacheEvict(db); + + pthread_mutex_unlock(&db->lock); } void @@ -401,10 +399,13 @@ DbClose(Db * db) return; } + pthread_mutex_lock(&db->lock); + DbMaxCacheSet(db, 0); DbCacheEvict(db); HashMapFree(db->cache); + pthread_mutex_unlock(&db->lock); pthread_mutex_destroy(&db->lock); Free(db); @@ -441,8 +442,6 @@ DbLockFromArr(Db * db, Array * args) { if (ref) { - pthread_mutex_lock(&ref->lock); - HashMapDelete(db->cache, hash); JsonFree(ref->json); StringArrayFree(ref->name); @@ -472,8 +471,6 @@ DbLockFromArr(Db * db, Array * args) db->leastRecent = db->mostRecent; } - pthread_mutex_unlock(&ref->lock); - pthread_mutex_destroy(&ref->lock); Free(ref); } ref = NULL; @@ -499,8 +496,6 @@ DbLockFromArr(Db * db, Array * args) { unsigned long diskTs = UtilLastModified(file); - pthread_mutex_lock(&ref->lock); - ref->fd = fd; ref->stream = stream; @@ -511,7 +506,6 @@ DbLockFromArr(Db * db, Array * args) if (!json) { - pthread_mutex_unlock(&ref->lock); StreamClose(ref->stream); ref = NULL; goto finish; @@ -585,9 +579,6 @@ DbLockFromArr(Db * db, Array * args) ref->fd = fd; ref->stream = stream; - pthread_mutex_init(&ref->lock, NULL); - pthread_mutex_lock(&ref->lock); - name = ArrayCreate(); for (i = 0; i < ArraySize(args); i++) { @@ -622,7 +613,10 @@ DbLockFromArr(Db * db, Array * args) } finish: - pthread_mutex_unlock(&db->lock); + if (!ref) + { + pthread_mutex_unlock(&db->lock); + } Free(file); Free(hash); @@ -654,12 +648,15 @@ DbCreate(Db * db, size_t nArgs,...) return NULL; } + pthread_mutex_lock(&db->lock); + file = DbFileName(db, args); if (UtilLastModified(file)) { Free(file); ArrayFree(args); + pthread_mutex_unlock(&db->lock); return NULL; } @@ -669,6 +666,7 @@ DbCreate(Db * db, size_t nArgs,...) Free(file); ArrayFree(args); Free(dir); + pthread_mutex_unlock(&db->lock); return NULL; } @@ -679,12 +677,16 @@ DbCreate(Db * db, size_t nArgs,...) if (!fp) { ArrayFree(args); + pthread_mutex_unlock(&db->lock); return NULL; } StreamPuts(fp, "{}"); StreamClose(fp); + /* DbLockFromArr() will lock again for us */ + pthread_mutex_unlock(&db->lock); + ret = DbLockFromArr(db, args); ArrayFree(args); @@ -719,8 +721,6 @@ DbDelete(Db * db, size_t nArgs,...) ref = HashMapGet(db->cache, hash); if (ref) { - pthread_mutex_lock(&ref->lock); - HashMapDelete(db->cache, hash); JsonFree(ref->json); StringArrayFree(ref->name); @@ -750,8 +750,6 @@ DbDelete(Db * db, size_t nArgs,...) db->leastRecent = db->mostRecent; } - pthread_mutex_unlock(&ref->lock); - pthread_mutex_destroy(&ref->lock); Free(ref); } @@ -802,8 +800,6 @@ DbUnlock(Db * db, DbRef * ref) return 0; } - pthread_mutex_lock(&db->lock); - lseek(ref->fd, 0L, SEEK_SET); if (ftruncate(ref->fd, 0) < 0) { @@ -841,15 +837,11 @@ DbUnlock(Db * db, DbRef * ref) destroy = 1; } - pthread_mutex_unlock(&ref->lock); - if (destroy) { JsonFree(ref->json); StringArrayFree(ref->name); - pthread_mutex_destroy(&ref->lock); - Free(ref); } @@ -874,11 +866,6 @@ DbExists(Db * db, size_t nArgs,...) return 0; } - /* - * Though it's not explicitly required, we lock the - * database before checking that an object exists to - * prevent any potential race conditions. - */ pthread_mutex_lock(&db->lock); file = DbFileName(db, args); @@ -917,12 +904,15 @@ DbList(Db * db, size_t nArgs,...) path = ArrayFromVarArgs(nArgs, ap); dir = DbDirName(db, path, 0); + pthread_mutex_lock(&db->lock); + files = opendir(dir); if (!files) { ArrayFree(path); ArrayFree(result); Free(dir); + pthread_mutex_unlock(&db->lock); return NULL; } while ((file = readdir(files))) @@ -944,6 +934,7 @@ DbList(Db * db, size_t nArgs,...) ArrayFree(path); Free(dir); + pthread_mutex_unlock(&db->lock); return result; }