Some checks failed
CI / build (push) Failing after 4m42s
- 添加缺失的 parser 辅助函数(parser_expect_string, parser_skip_value, parser_number_str) - 修复 Makefile:添加 test_cache 构建规则,补充 cache.c 依赖 - 修复 test_cache.c:使用 cache_stats() API 替代直接访问内部字段 - 修复 test_config.c:更新 config_merge() 调用参数以匹配新签名 - 修复 CI 工作流:使用 make build-all 构建依赖
401 lines
12 KiB
C
401 lines
12 KiB
C
/**
|
||
* cache.c - 内存响应缓存实现
|
||
*
|
||
* LRU + TTL 缓存,哈希表 + 双向链表。
|
||
* 线程安全:所有操作受 pthread_mutex 保护。
|
||
*
|
||
* @author xfy
|
||
*/
|
||
|
||
#include "cache.h"
|
||
#include "log.h"
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <pthread.h>
|
||
|
||
#define CACHE_DEFAULT_MAX_SIZE (64 * 1024 * 1024) /**< 默认 64MB */
|
||
#define CACHE_DEFAULT_TTL_SECONDS 60 /**< 默认 60 秒 */
|
||
#define CACHE_DEFAULT_MAX_ENTRY_SIZE (1024 * 1024) /**< 默认 1MB */
|
||
#define CACHE_DEFAULT_BUCKET_COUNT 512 /**< 哈希桶数 */
|
||
|
||
/**
|
||
* cache_node_t - 内部链表节点
|
||
*
|
||
* 双向链表实现 LRU:头节点为最近使用,尾节点为最久未使用。
|
||
*/
|
||
typedef struct cache_node {
|
||
char *key; /**< 键(独立拷贝) */
|
||
char *header; /**< 响应头 */
|
||
size_t header_len; /**< 响应头长度 */
|
||
char *body; /**< 响应体 */
|
||
size_t body_len; /**< 响应体长度 */
|
||
time_t mtime; /**< 文件修改时间 */
|
||
time_t expires_at; /**< TTL 过期时间 */
|
||
struct cache_node *next; /**< LRU 链表:前驱(更近) */
|
||
struct cache_node *prev; /**< LRU 链表:后继(更久) */
|
||
struct cache_node *hash_next; /**< 哈希链表 */
|
||
} cache_node_t;
|
||
|
||
struct cocoon_cache {
|
||
cache_node_t *lru_head; /**< LRU 头(最近使用) */
|
||
cache_node_t *lru_tail; /**< LRU 尾(最久未使用) */
|
||
cache_node_t **buckets; /**< 哈希桶数组 */
|
||
size_t bucket_count; /**< 桶数量 */
|
||
size_t max_size; /**< 最大总容量 */
|
||
size_t max_entry_size; /**< 单条最大大小 */
|
||
uint32_t ttl_seconds; /**< TTL 秒数 */
|
||
size_t total_size; /**< 当前总大小 */
|
||
size_t hits; /**< 命中次数 */
|
||
size_t misses; /**< 未命中次数 */
|
||
size_t evictions; /**< 淘汰次数 */
|
||
size_t expirations; /**< 过期次数 */
|
||
pthread_mutex_t lock; /**< 线程锁 */
|
||
};
|
||
|
||
/* === 内部辅助函数 === */
|
||
|
||
/**
|
||
* hash_fn - djb2 哈希
|
||
*/
|
||
static size_t hash_fn(const char *str) {
|
||
size_t hash = 5381;
|
||
int c;
|
||
while ((c = (unsigned char)*str++)) {
|
||
hash = ((hash << 5) + hash) + c;
|
||
}
|
||
return hash;
|
||
}
|
||
|
||
/**
|
||
* lru_move_to_head - 将节点移到 LRU 链表头部(最近使用)
|
||
*/
|
||
static void lru_move_to_head(cocoon_cache_t *cache, cache_node_t *node) {
|
||
if (cache->lru_head == node) return;
|
||
|
||
/* 从原位置移除 */
|
||
if (node->prev) node->prev->next = node->next;
|
||
if (node->next) node->next->prev = node->prev;
|
||
if (cache->lru_tail == node) cache->lru_tail = node->prev;
|
||
|
||
/* 插入头部 */
|
||
node->next = cache->lru_head;
|
||
node->prev = NULL;
|
||
if (cache->lru_head) cache->lru_head->prev = node;
|
||
cache->lru_head = node;
|
||
if (!cache->lru_tail) cache->lru_tail = node;
|
||
}
|
||
|
||
/**
|
||
* lru_remove - 从 LRU 链表中移除节点
|
||
*/
|
||
static void lru_remove(cocoon_cache_t *cache, cache_node_t *node) {
|
||
if (node->prev) node->prev->next = node->next;
|
||
else cache->lru_head = node->next;
|
||
|
||
if (node->next) node->next->prev = node->prev;
|
||
else cache->lru_tail = node->prev;
|
||
|
||
node->prev = NULL;
|
||
node->next = NULL;
|
||
}
|
||
|
||
/**
|
||
* hash_remove - 从哈希表中移除节点
|
||
*/
|
||
static void hash_remove(cocoon_cache_t *cache, const char *key) {
|
||
size_t idx = hash_fn(key) % cache->bucket_count;
|
||
cache_node_t **pp = &cache->buckets[idx];
|
||
while (*pp) {
|
||
if (strcmp((*pp)->key, key) == 0) {
|
||
*pp = (*pp)->hash_next;
|
||
return;
|
||
}
|
||
pp = &(*pp)->hash_next;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* hash_insert - 插入到哈希表
|
||
*/
|
||
static void hash_insert(cocoon_cache_t *cache, cache_node_t *node) {
|
||
size_t idx = hash_fn(node->key) % cache->bucket_count;
|
||
node->hash_next = cache->buckets[idx];
|
||
cache->buckets[idx] = node;
|
||
}
|
||
|
||
/**
|
||
* hash_find - 在哈希表中查找节点
|
||
*/
|
||
static cache_node_t *hash_find(cocoon_cache_t *cache, const char *key) {
|
||
size_t idx = hash_fn(key) % cache->bucket_count;
|
||
cache_node_t *node = cache->buckets[idx];
|
||
while (node) {
|
||
if (strcmp(node->key, key) == 0) {
|
||
return node;
|
||
}
|
||
node = node->hash_next;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* node_free - 释放单个节点
|
||
*/
|
||
static void node_free(cache_node_t *node) {
|
||
if (!node) return;
|
||
free(node->key);
|
||
free(node->header);
|
||
free(node->body);
|
||
free(node);
|
||
}
|
||
|
||
/**
|
||
* evict_one - 淘汰最久未使用的条目
|
||
*/
|
||
static void evict_one(cocoon_cache_t *cache) {
|
||
cache_node_t *victim = cache->lru_tail;
|
||
if (!victim) return;
|
||
|
||
lru_remove(cache, victim);
|
||
hash_remove(cache, victim->key);
|
||
|
||
size_t entry_size = victim->header_len + victim->body_len;
|
||
cache->total_size -= entry_size;
|
||
cache->evictions++;
|
||
|
||
log_debug("[Cache] LRU 淘汰: %s (大小 %zu 字节)", victim->key, entry_size);
|
||
node_free(victim);
|
||
}
|
||
|
||
/**
|
||
* make_room - 为新条目腾出空间
|
||
*/
|
||
static void make_room(cocoon_cache_t *cache, size_t needed) {
|
||
while (cache->total_size + needed > cache->max_size && cache->lru_tail) {
|
||
evict_one(cache);
|
||
}
|
||
}
|
||
|
||
/* === 公共 API === */
|
||
|
||
cocoon_cache_t *cache_create(size_t max_size, uint32_t ttl_seconds, size_t max_entry_size) {
|
||
cocoon_cache_t *cache = (cocoon_cache_t *)calloc(1, sizeof(cocoon_cache_t));
|
||
if (!cache) {
|
||
log_error("[Cache] 分配缓存结构失败");
|
||
return NULL;
|
||
}
|
||
|
||
cache->max_size = max_size > 0 ? max_size : CACHE_DEFAULT_MAX_SIZE;
|
||
cache->ttl_seconds = ttl_seconds > 0 ? ttl_seconds : CACHE_DEFAULT_TTL_SECONDS;
|
||
cache->max_entry_size = max_entry_size > 0 ? max_entry_size : CACHE_DEFAULT_MAX_ENTRY_SIZE;
|
||
cache->bucket_count = CACHE_DEFAULT_BUCKET_COUNT;
|
||
cache->buckets = (cache_node_t **)calloc(cache->bucket_count, sizeof(cache_node_t *));
|
||
if (!cache->buckets) {
|
||
log_error("[Cache] 分配哈希桶失败");
|
||
free(cache);
|
||
return NULL;
|
||
}
|
||
|
||
if (pthread_mutex_init(&cache->lock, NULL) != 0) {
|
||
log_error("[Cache] 初始化互斥锁失败");
|
||
free(cache->buckets);
|
||
free(cache);
|
||
return NULL;
|
||
}
|
||
|
||
log_info("[Cache] 已创建: 最大容量 %zu 字节, TTL %u 秒, 单条上限 %zu 字节",
|
||
cache->max_size, cache->ttl_seconds, cache->max_entry_size);
|
||
return cache;
|
||
}
|
||
|
||
void cache_destroy(cocoon_cache_t *cache) {
|
||
if (!cache) return;
|
||
|
||
pthread_mutex_lock(&cache->lock);
|
||
|
||
cache_node_t *node = cache->lru_head;
|
||
while (node) {
|
||
cache_node_t *next = node->next;
|
||
node_free(node);
|
||
node = next;
|
||
}
|
||
|
||
free(cache->buckets);
|
||
pthread_mutex_unlock(&cache->lock);
|
||
pthread_mutex_destroy(&cache->lock);
|
||
free(cache);
|
||
|
||
log_info("[Cache] 已销毁");
|
||
}
|
||
|
||
const cocoon_cache_entry_t *cache_get(cocoon_cache_t *cache, const char *key, time_t mtime) {
|
||
if (!cache || !key) return NULL;
|
||
|
||
pthread_mutex_lock(&cache->lock);
|
||
|
||
cache_node_t *node = hash_find(cache, key);
|
||
if (!node) {
|
||
cache->misses++;
|
||
pthread_mutex_unlock(&cache->lock);
|
||
return NULL;
|
||
}
|
||
|
||
/* 检查 TTL */
|
||
time_t now = time(NULL);
|
||
if (now > node->expires_at) {
|
||
cache->expirations++;
|
||
cache->misses++;
|
||
lru_remove(cache, node);
|
||
hash_remove(cache, key);
|
||
cache->total_size -= (node->header_len + node->body_len);
|
||
node_free(node);
|
||
pthread_mutex_unlock(&cache->lock);
|
||
return NULL;
|
||
}
|
||
|
||
/* 检查文件是否被修改 */
|
||
if (node->mtime != mtime) {
|
||
cache->misses++;
|
||
lru_remove(cache, node);
|
||
hash_remove(cache, key);
|
||
cache->total_size -= (node->header_len + node->body_len);
|
||
node_free(node);
|
||
pthread_mutex_unlock(&cache->lock);
|
||
return NULL;
|
||
}
|
||
|
||
/* 命中:移到 LRU 头部 */
|
||
lru_move_to_head(cache, node);
|
||
cache->hits++;
|
||
|
||
/* 构造返回结构(栈上临时,调用者需立即使用) */
|
||
static __thread cocoon_cache_entry_t entry;
|
||
entry.key = node->key;
|
||
entry.header = node->header;
|
||
entry.header_len = node->header_len;
|
||
entry.body = node->body;
|
||
entry.body_len = node->body_len;
|
||
entry.mtime = node->mtime;
|
||
entry.expires_at = node->expires_at;
|
||
|
||
pthread_mutex_unlock(&cache->lock);
|
||
return &entry;
|
||
}
|
||
|
||
void cache_put(cocoon_cache_t *cache, const char *key,
|
||
const char *header, size_t header_len,
|
||
const char *body, size_t body_len,
|
||
time_t mtime) {
|
||
if (!cache || !key || !header || !body) return;
|
||
|
||
size_t entry_size = header_len + body_len;
|
||
|
||
/* 超过单条上限,跳过 */
|
||
if (entry_size > cache->max_entry_size) {
|
||
log_debug("[Cache] 条目 %s 大小 %zu 超过上限 %zu,不缓存", key, entry_size, cache->max_entry_size);
|
||
return;
|
||
}
|
||
|
||
pthread_mutex_lock(&cache->lock);
|
||
|
||
/* 如果已存在,先删除旧条目 */
|
||
cache_node_t *existing = hash_find(cache, key);
|
||
if (existing) {
|
||
lru_remove(cache, existing);
|
||
hash_remove(cache, key);
|
||
cache->total_size -= (existing->header_len + existing->body_len);
|
||
node_free(existing);
|
||
}
|
||
|
||
/* 腾出空间 */
|
||
make_room(cache, entry_size);
|
||
|
||
/* 创建新节点 */
|
||
cache_node_t *node = (cache_node_t *)calloc(1, sizeof(cache_node_t));
|
||
if (!node) {
|
||
pthread_mutex_unlock(&cache->lock);
|
||
log_error("[Cache] 分配节点失败");
|
||
return;
|
||
}
|
||
|
||
node->key = strdup(key);
|
||
node->header = (char *)malloc(header_len);
|
||
node->body = (char *)malloc(body_len);
|
||
if (!node->key || !node->header || !node->body) {
|
||
node_free(node);
|
||
pthread_mutex_unlock(&cache->lock);
|
||
log_error("[Cache] 拷贝数据失败");
|
||
return;
|
||
}
|
||
|
||
memcpy(node->header, header, header_len);
|
||
node->header_len = header_len;
|
||
memcpy(node->body, body, body_len);
|
||
node->body_len = body_len;
|
||
node->mtime = mtime;
|
||
node->expires_at = time(NULL) + cache->ttl_seconds;
|
||
|
||
/* 插入 LRU 头部和哈希表 */
|
||
node->next = cache->lru_head;
|
||
if (cache->lru_head) cache->lru_head->prev = node;
|
||
cache->lru_head = node;
|
||
if (!cache->lru_tail) cache->lru_tail = node;
|
||
|
||
hash_insert(cache, node);
|
||
cache->total_size += entry_size;
|
||
|
||
pthread_mutex_unlock(&cache->lock);
|
||
|
||
log_debug("[Cache] 已缓存: %s (头 %zu + 体 %zu = %zu 字节, 总计 %zu 字节)",
|
||
key, header_len, body_len, entry_size, cache->total_size);
|
||
}
|
||
|
||
void cache_stats(cocoon_cache_t *cache, cocoon_cache_stats_t *stats) {
|
||
if (!cache || !stats) return;
|
||
|
||
pthread_mutex_lock(&cache->lock);
|
||
stats->entries = 0;
|
||
cache_node_t *node = cache->lru_head;
|
||
while (node) {
|
||
stats->entries++;
|
||
node = node->next;
|
||
}
|
||
stats->total_size = cache->total_size;
|
||
stats->hits = cache->hits;
|
||
stats->misses = cache->misses;
|
||
stats->evictions = cache->evictions;
|
||
stats->expirations = cache->expirations;
|
||
pthread_mutex_unlock(&cache->lock);
|
||
}
|
||
|
||
void cache_clear(cocoon_cache_t *cache) {
|
||
if (!cache) return;
|
||
|
||
pthread_mutex_lock(&cache->lock);
|
||
|
||
cache_node_t *node = cache->lru_head;
|
||
while (node) {
|
||
cache_node_t *next = node->next;
|
||
node_free(node);
|
||
node = next;
|
||
}
|
||
cache->lru_head = NULL;
|
||
cache->lru_tail = NULL;
|
||
memset(cache->buckets, 0, cache->bucket_count * sizeof(cache_node_t *));
|
||
cache->total_size = 0;
|
||
cache->hits = 0;
|
||
cache->misses = 0;
|
||
cache->evictions = 0;
|
||
cache->expirations = 0;
|
||
|
||
pthread_mutex_unlock(&cache->lock);
|
||
|
||
log_info("[Cache] 已清空");
|
||
}
|
||
|
||
/* 缓存辅助函数:判断文件是否适合缓存 */
|
||
bool cache_is_eligible(const cocoon_cache_t *cache, int64_t file_size) {
|
||
if (!cache || file_size <= 0) return false;
|
||
return cache->max_entry_size > 0 && (size_t)file_size <= cache->max_entry_size;
|
||
}
|