forked from lda/telodendria
Fixed some caching bugs in Db.
Note that Db has the potential to deadlock when caching is being used, and when caching isn't being used, an inconsistent state can occur. Future changes to Db will fix both of these issues.
This commit is contained in:
parent
737e060243
commit
313249ca88
2 changed files with 95 additions and 32 deletions
9
TODO.txt
9
TODO.txt
|
@ -82,6 +82,9 @@ Milestone: v0.3.0
|
||||||
[ ] Refactor dev pages so function description and
|
[ ] Refactor dev pages so function description and
|
||||||
return value are in the same location.
|
return value are in the same location.
|
||||||
|
|
||||||
|
[ ] Debug memory leaks with caching
|
||||||
|
[ ] Debug OpenSSL
|
||||||
|
|
||||||
[~] Client-Server API
|
[~] Client-Server API
|
||||||
[x] 4: Token-based user registration
|
[x] 4: Token-based user registration
|
||||||
[x] Implement user-interactive auth flow
|
[x] Implement user-interactive auth flow
|
||||||
|
@ -90,9 +93,9 @@ Milestone: v0.3.0
|
||||||
flow
|
flow
|
||||||
- Ensure that registration tokens can be used even if
|
- Ensure that registration tokens can be used even if
|
||||||
registration is disabled.
|
registration is disabled.
|
||||||
[~] User-Interactive fallback
|
[x] User-Interactive fallback
|
||||||
[~] Password
|
[x] Password
|
||||||
[~] Registration token
|
[x] Registration token
|
||||||
[~] 4: Account management
|
[~] 4: Account management
|
||||||
[~] Deactivate
|
[~] Deactivate
|
||||||
[x] Make sure UserLogin() fails if user is deactivated.
|
[x] Make sure UserLogin() fails if user is deactivated.
|
||||||
|
|
104
src/Db.c
104
src/Db.c
|
@ -28,6 +28,7 @@
|
||||||
#include <Util.h>
|
#include <Util.h>
|
||||||
#include <Str.h>
|
#include <Str.h>
|
||||||
#include <Stream.h>
|
#include <Stream.h>
|
||||||
|
#include <Log.h>
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <dirent.h>
|
#include <dirent.h>
|
||||||
|
@ -287,7 +288,7 @@ DbCacheEvict(Db * db)
|
||||||
|
|
||||||
while (ref && db->cacheSize > db->maxCache)
|
while (ref && db->cacheSize > db->maxCache)
|
||||||
{
|
{
|
||||||
char *hash = DbHashKey(ref->name);
|
char *hash;
|
||||||
|
|
||||||
if (pthread_mutex_trylock(&ref->lock) != 0)
|
if (pthread_mutex_trylock(&ref->lock) != 0)
|
||||||
{
|
{
|
||||||
|
@ -308,15 +309,23 @@ DbCacheEvict(Db * db)
|
||||||
|
|
||||||
db->cacheSize -= ref->size;
|
db->cacheSize -= ref->size;
|
||||||
|
|
||||||
ref->next->prev = ref->prev;
|
if (ref->next)
|
||||||
if (!ref->prev)
|
|
||||||
{
|
{
|
||||||
db->leastRecent = ref->next;
|
ref->next->prev = ref->prev;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
db->mostRecent = ref->prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref->prev)
|
||||||
{
|
{
|
||||||
ref->prev->next = ref->next;
|
ref->prev->next = ref->next;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
db->leastRecent = ref->next;
|
||||||
|
}
|
||||||
|
|
||||||
tmp = ref->next;
|
tmp = ref->next;
|
||||||
Free(ref);
|
Free(ref);
|
||||||
|
@ -348,6 +357,7 @@ DbOpen(char *dir, size_t cache)
|
||||||
|
|
||||||
db->mostRecent = NULL;
|
db->mostRecent = NULL;
|
||||||
db->leastRecent = NULL;
|
db->leastRecent = NULL;
|
||||||
|
db->cacheSize = 0;
|
||||||
|
|
||||||
if (db->maxCache)
|
if (db->maxCache)
|
||||||
{
|
{
|
||||||
|
@ -374,31 +384,29 @@ DbMaxCacheSet(Db * db, size_t cache)
|
||||||
}
|
}
|
||||||
|
|
||||||
db->maxCache = cache;
|
db->maxCache = cache;
|
||||||
|
if (db->maxCache && !db->cache)
|
||||||
|
{
|
||||||
|
db->cache = HashMapCreate();
|
||||||
|
db->cacheSize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
DbCacheEvict(db);
|
DbCacheEvict(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
DbClose(Db * db)
|
DbClose(Db * db)
|
||||||
{
|
{
|
||||||
char *key;
|
|
||||||
DbRef *val;
|
|
||||||
|
|
||||||
if (!db)
|
if (!db)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pthread_mutex_destroy(&db->lock);
|
DbMaxCacheSet(db, 0);
|
||||||
|
DbCacheEvict(db);
|
||||||
while (HashMapIterate(db->cache, &key, (void **) &val))
|
|
||||||
{
|
|
||||||
JsonFree(val->json);
|
|
||||||
StringArrayFree(val->name);
|
|
||||||
pthread_mutex_destroy(&val->lock);
|
|
||||||
Free(val);
|
|
||||||
}
|
|
||||||
HashMapFree(db->cache);
|
HashMapFree(db->cache);
|
||||||
|
|
||||||
|
pthread_mutex_destroy(&db->lock);
|
||||||
|
|
||||||
Free(db);
|
Free(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -459,6 +467,11 @@ DbLockFromArr(Db * db, Array * args)
|
||||||
db->leastRecent = ref->next;
|
db->leastRecent = ref->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!db->leastRecent)
|
||||||
|
{
|
||||||
|
db->leastRecent = db->mostRecent;
|
||||||
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&ref->lock);
|
pthread_mutex_unlock(&ref->lock);
|
||||||
pthread_mutex_destroy(&ref->lock);
|
pthread_mutex_destroy(&ref->lock);
|
||||||
Free(ref);
|
Free(ref);
|
||||||
|
@ -488,6 +501,9 @@ DbLockFromArr(Db * db, Array * args)
|
||||||
|
|
||||||
pthread_mutex_lock(&ref->lock);
|
pthread_mutex_lock(&ref->lock);
|
||||||
|
|
||||||
|
ref->fd = fd;
|
||||||
|
ref->stream = stream;
|
||||||
|
|
||||||
if (diskTs > ref->ts)
|
if (diskTs > ref->ts)
|
||||||
{
|
{
|
||||||
/* File was modified on disk since it was cached */
|
/* File was modified on disk since it was cached */
|
||||||
|
@ -523,9 +539,21 @@ DbLockFromArr(Db * db, Array * args)
|
||||||
|
|
||||||
ref->prev = db->mostRecent;
|
ref->prev = db->mostRecent;
|
||||||
ref->next = NULL;
|
ref->next = NULL;
|
||||||
|
if (db->mostRecent)
|
||||||
|
{
|
||||||
|
db->mostRecent->next = ref;
|
||||||
|
}
|
||||||
db->mostRecent = ref;
|
db->mostRecent = ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* If there is no least recent, this is the only
|
||||||
|
* thing in the cache, so it is also least recent.
|
||||||
|
*/
|
||||||
|
if (!db->leastRecent)
|
||||||
|
{
|
||||||
|
db->leastRecent = ref;
|
||||||
|
}
|
||||||
|
|
||||||
/* The file on disk may be larger than what we have in memory,
|
/* The file on disk may be larger than what we have in memory,
|
||||||
* which may require items in cache to be evicted. */
|
* which may require items in cache to be evicted. */
|
||||||
DbCacheEvict(db);
|
DbCacheEvict(db);
|
||||||
|
@ -567,7 +595,7 @@ DbLockFromArr(Db * db, Array * args)
|
||||||
}
|
}
|
||||||
ref->name = name;
|
ref->name = name;
|
||||||
|
|
||||||
if (db->maxCache)
|
if (db->cache)
|
||||||
{
|
{
|
||||||
ref->ts = UtilServerTs();
|
ref->ts = UtilServerTs();
|
||||||
ref->size = DbComputeSize(ref->json);
|
ref->size = DbComputeSize(ref->json);
|
||||||
|
@ -576,8 +604,17 @@ DbLockFromArr(Db * db, Array * args)
|
||||||
|
|
||||||
ref->next = NULL;
|
ref->next = NULL;
|
||||||
ref->prev = db->mostRecent;
|
ref->prev = db->mostRecent;
|
||||||
|
if (db->mostRecent)
|
||||||
|
{
|
||||||
|
db->mostRecent->next = ref;
|
||||||
|
}
|
||||||
db->mostRecent = ref;
|
db->mostRecent = ref;
|
||||||
|
|
||||||
|
if (!db->leastRecent)
|
||||||
|
{
|
||||||
|
db->leastRecent = ref;
|
||||||
|
}
|
||||||
|
|
||||||
/* Adding this item to the cache may case it to grow too
|
/* Adding this item to the cache may case it to grow too
|
||||||
* large, requiring some items to be evicted */
|
* large, requiring some items to be evicted */
|
||||||
DbCacheEvict(db);
|
DbCacheEvict(db);
|
||||||
|
@ -708,6 +745,11 @@ DbDelete(Db * db, size_t nArgs,...)
|
||||||
db->leastRecent = ref->next;
|
db->leastRecent = ref->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!db->leastRecent)
|
||||||
|
{
|
||||||
|
db->leastRecent = db->mostRecent;
|
||||||
|
}
|
||||||
|
|
||||||
pthread_mutex_unlock(&ref->lock);
|
pthread_mutex_unlock(&ref->lock);
|
||||||
pthread_mutex_destroy(&ref->lock);
|
pthread_mutex_destroy(&ref->lock);
|
||||||
Free(ref);
|
Free(ref);
|
||||||
|
@ -753,6 +795,8 @@ DbLock(Db * db, size_t nArgs,...)
|
||||||
int
|
int
|
||||||
DbUnlock(Db * db, DbRef * ref)
|
DbUnlock(Db * db, DbRef * ref)
|
||||||
{
|
{
|
||||||
|
int destroy;
|
||||||
|
|
||||||
if (!db || !ref)
|
if (!db || !ref)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -764,14 +808,19 @@ DbUnlock(Db * db, DbRef * ref)
|
||||||
if (ftruncate(ref->fd, 0) < 0)
|
if (ftruncate(ref->fd, 0) < 0)
|
||||||
{
|
{
|
||||||
pthread_mutex_unlock(&db->lock);
|
pthread_mutex_unlock(&db->lock);
|
||||||
|
Log(LOG_ERR, "Failed to truncate file on disk.");
|
||||||
|
Log(LOG_ERR, "Error: %s", strerror(errno));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
JsonEncode(ref->json, ref->stream, JSON_DEFAULT);
|
JsonEncode(ref->json, ref->stream, JSON_DEFAULT);
|
||||||
|
|
||||||
StreamClose(ref->stream);
|
StreamClose(ref->stream);
|
||||||
|
|
||||||
if (db->maxCache)
|
if (db->cache)
|
||||||
|
{
|
||||||
|
char *key = DbHashKey(ref->name);
|
||||||
|
|
||||||
|
if (HashMapGet(db->cache, key))
|
||||||
{
|
{
|
||||||
db->cacheSize -= ref->size;
|
db->cacheSize -= ref->size;
|
||||||
ref->size = DbComputeSize(ref->json);
|
ref->size = DbComputeSize(ref->json);
|
||||||
|
@ -781,14 +830,24 @@ DbUnlock(Db * db, DbRef * ref)
|
||||||
* its size, it may have filled the cache and require some
|
* its size, it may have filled the cache and require some
|
||||||
* items to be evicted. */
|
* items to be evicted. */
|
||||||
DbCacheEvict(db);
|
DbCacheEvict(db);
|
||||||
pthread_mutex_unlock(&ref->lock);
|
|
||||||
|
destroy = 0;
|
||||||
|
} else {
|
||||||
|
destroy = 1;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
Free(key);
|
||||||
|
} else {
|
||||||
|
destroy = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_unlock(&ref->lock);
|
||||||
|
|
||||||
|
if (destroy)
|
||||||
{
|
{
|
||||||
JsonFree(ref->json);
|
JsonFree(ref->json);
|
||||||
StringArrayFree(ref->name);
|
StringArrayFree(ref->name);
|
||||||
|
|
||||||
pthread_mutex_unlock(&ref->lock);
|
|
||||||
pthread_mutex_destroy(&ref->lock);
|
pthread_mutex_destroy(&ref->lock);
|
||||||
|
|
||||||
Free(ref);
|
Free(ref);
|
||||||
|
@ -913,3 +972,4 @@ DbJsonSet(DbRef * ref, HashMap * json)
|
||||||
ref->json = JsonDuplicate(json);
|
ref->json = JsonDuplicate(json);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue