221 lines
5.8 KiB
C
221 lines
5.8 KiB
C
#include "cache.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <pthread.h>
|
|
|
|
struct crt_cache {
|
|
crt_cache_entry_t** buckets;
|
|
size_t bucket_count;
|
|
size_t max_entries;
|
|
size_t current_entries;
|
|
time_t default_ttl;
|
|
pthread_rwlock_t lock;
|
|
};
|
|
|
|
static size_t hash_key(const char* key) {
|
|
size_t hash = 5381;
|
|
int c;
|
|
while ((c = *key++)) {
|
|
hash = ((hash << 5) + hash) + c;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
crt_cache_t* crt_cache_create(size_t max_entries, time_t default_ttl_sec) {
|
|
crt_cache_t* cache = calloc(1, sizeof(crt_cache_t));
|
|
if (!cache) return NULL;
|
|
|
|
cache->bucket_count = 1024;
|
|
cache->buckets = calloc(cache->bucket_count, sizeof(crt_cache_entry_t*));
|
|
if (!cache->buckets) {
|
|
free(cache);
|
|
return NULL;
|
|
}
|
|
|
|
cache->max_entries = max_entries;
|
|
cache->default_ttl = default_ttl_sec;
|
|
cache->current_entries = 0;
|
|
pthread_rwlock_init(&cache->lock, NULL);
|
|
|
|
return cache;
|
|
}
|
|
|
|
void crt_cache_destroy(crt_cache_t* cache) {
|
|
if (!cache) return;
|
|
crt_cache_clear(cache);
|
|
free(cache->buckets);
|
|
pthread_rwlock_destroy(&cache->lock);
|
|
free(cache);
|
|
}
|
|
|
|
bool crt_cache_set(crt_cache_t* cache, const char* key, const void* data, size_t len) {
|
|
return crt_cache_set_ttl(cache, key, data, len, cache->default_ttl);
|
|
}
|
|
|
|
bool crt_cache_set_ttl(crt_cache_t* cache, const char* key, const void* data, size_t len, time_t ttl_sec) {
|
|
if (!cache || !key || !data || len == 0) return false;
|
|
|
|
pthread_rwlock_wrlock(&cache->lock);
|
|
|
|
if (cache->current_entries >= cache->max_entries) {
|
|
crt_cache_evict_expired(cache);
|
|
}
|
|
|
|
size_t idx = hash_key(key) % cache->bucket_count;
|
|
crt_cache_entry_t* entry = cache->buckets[idx];
|
|
|
|
while (entry) {
|
|
if (strcmp(entry->key, key) == 0) {
|
|
free(entry->data);
|
|
entry->data = malloc(len);
|
|
if (!entry->data) {
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return false;
|
|
}
|
|
memcpy(entry->data, data, len);
|
|
entry->len = len;
|
|
entry->expires_at = time(NULL) + ttl_sec;
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return true;
|
|
}
|
|
entry = entry->next;
|
|
}
|
|
|
|
entry = malloc(sizeof(crt_cache_entry_t));
|
|
if (!entry) {
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return false;
|
|
}
|
|
|
|
strncpy(entry->key, key, sizeof(entry->key) - 1);
|
|
entry->data = malloc(len);
|
|
if (!entry->data) {
|
|
free(entry);
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return false;
|
|
}
|
|
memcpy(entry->data, data, len);
|
|
entry->len = len;
|
|
entry->expires_at = time(NULL) + ttl_sec;
|
|
entry->next = cache->buckets[idx];
|
|
cache->buckets[idx] = entry;
|
|
cache->current_entries++;
|
|
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return true;
|
|
}
|
|
|
|
bool crt_cache_get(crt_cache_t* cache, const char* key, void* out, size_t* len) {
|
|
if (!cache || !key || !out || !len) return false;
|
|
|
|
pthread_rwlock_rdlock(&cache->lock);
|
|
|
|
size_t idx = hash_key(key) % cache->bucket_count;
|
|
crt_cache_entry_t* entry = cache->buckets[idx];
|
|
time_t now = time(NULL);
|
|
|
|
while (entry) {
|
|
if (strcmp(entry->key, key) == 0) {
|
|
if (entry->expires_at < now) {
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
crt_cache_delete(cache, key);
|
|
return false;
|
|
}
|
|
memcpy(out, entry->data, entry->len);
|
|
*len = entry->len;
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return true;
|
|
}
|
|
entry = entry->next;
|
|
}
|
|
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return false;
|
|
}
|
|
|
|
bool crt_cache_delete(crt_cache_t* cache, const char* key) {
|
|
if (!cache || !key) return false;
|
|
|
|
pthread_rwlock_wrlock(&cache->lock);
|
|
|
|
size_t idx = hash_key(key) % cache->bucket_count;
|
|
crt_cache_entry_t* entry = cache->buckets[idx];
|
|
crt_cache_entry_t* prev = NULL;
|
|
|
|
while (entry) {
|
|
if (strcmp(entry->key, key) == 0) {
|
|
if (prev) {
|
|
prev->next = entry->next;
|
|
} else {
|
|
cache->buckets[idx] = entry->next;
|
|
}
|
|
free(entry->data);
|
|
free(entry);
|
|
cache->current_entries--;
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return true;
|
|
}
|
|
prev = entry;
|
|
entry = entry->next;
|
|
}
|
|
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return false;
|
|
}
|
|
|
|
void crt_cache_clear(crt_cache_t* cache) {
|
|
if (!cache) return;
|
|
|
|
pthread_rwlock_wrlock(&cache->lock);
|
|
|
|
for (size_t i = 0; i < cache->bucket_count; i++) {
|
|
crt_cache_entry_t* entry = cache->buckets[i];
|
|
while (entry) {
|
|
crt_cache_entry_t* next = entry->next;
|
|
free(entry->data);
|
|
free(entry);
|
|
entry = next;
|
|
}
|
|
cache->buckets[i] = NULL;
|
|
}
|
|
cache->current_entries = 0;
|
|
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
}
|
|
|
|
size_t crt_cache_size(crt_cache_t* cache) {
|
|
if (!cache) return 0;
|
|
pthread_rwlock_rdlock(&cache->lock);
|
|
size_t size = cache->current_entries;
|
|
pthread_rwlock_unlock(&cache->lock);
|
|
return size;
|
|
}
|
|
|
|
void crt_cache_evict_expired(crt_cache_t* cache) {
|
|
if (!cache) return;
|
|
|
|
time_t now = time(NULL);
|
|
|
|
for (size_t i = 0; i < cache->bucket_count; i++) {
|
|
crt_cache_entry_t* entry = cache->buckets[i];
|
|
crt_cache_entry_t* prev = NULL;
|
|
|
|
while (entry) {
|
|
crt_cache_entry_t* next = entry->next;
|
|
if (entry->expires_at < now) {
|
|
if (prev) {
|
|
prev->next = next;
|
|
} else {
|
|
cache->buckets[i] = next;
|
|
}
|
|
free(entry->data);
|
|
free(entry);
|
|
cache->current_entries--;
|
|
} else {
|
|
prev = entry;
|
|
}
|
|
entry = next;
|
|
}
|
|
}
|
|
}
|