feat(plugin): 插件热重载支持(SIGUSR1)
- plugin.c/plugin.h: 新增 cocoon_plugin_reload() 热重载 API - 自动存储已加载插件路径,卸载后按原路径重新加载 - main.c: 注册 SIGUSR1 信号处理器,触发插件热重载 - 添加 --help 中 Signals 说明文档 - 集成测试:新增 2 项热重载测试(功能验证 + 日志验证) - 全部 142 个单元测试 + 77 项集成测试通过
This commit is contained in:
parent
4b647cd540
commit
80385d9657
26
main.c
26
main.c
@ -11,15 +11,22 @@
|
||||
#include "config.h"
|
||||
#include "platform.h"
|
||||
#include "access_log.h"
|
||||
#include "plugin.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
/**
|
||||
* g_ctx - 全局服务器上下文(用于信号处理)
|
||||
*/
|
||||
static server_context_t *g_ctx = NULL;
|
||||
|
||||
/**
|
||||
* g_reload_plugins - 插件热重载标志(由 SIGUSR1 触发)
|
||||
*/
|
||||
static volatile int g_reload_plugins = 0;
|
||||
|
||||
/**
|
||||
* signal_handler - 优雅关闭信号处理
|
||||
*
|
||||
@ -35,6 +42,20 @@ static void signal_handler(int sig) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* reload_handler - 插件热重载信号处理
|
||||
*
|
||||
* 捕获 SIGUSR1,设置热重载标志。
|
||||
* 主循环中检测到标志后执行 cocoon_plugin_reload()。
|
||||
*
|
||||
* @param sig 信号编号
|
||||
*/
|
||||
static void reload_handler(int sig) {
|
||||
(void)sig;
|
||||
printf("\n[Cocoon] 收到 SIGUSR1,热重载插件...\n");
|
||||
cocoon_plugin_reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* print_usage - 打印使用说明
|
||||
*
|
||||
@ -62,7 +83,9 @@ static void print_usage(const char *prog) {
|
||||
printf(" --auth-pass <pass> Basic Auth 密码\n");
|
||||
printf(" --rate-limit <n> 每秒最大请求数(限流)\n");
|
||||
printf(" --plugin <path> 加载插件(可多次指定)\n");
|
||||
printf("\nExample:\n");
|
||||
printf("\nSignals:\n");
|
||||
printf(" SIGUSR1 热重载所有插件(无需重启服务器)\n");
|
||||
printf("\nExample:\n");
|
||||
printf(" %s -c cocoon.json\n", prog);
|
||||
printf(" %s -r ./www -p 8080\n", prog);
|
||||
printf(" %s -c cocoon.json -p 9090 # 命令行覆盖配置文件的端口\n", prog);
|
||||
@ -266,6 +289,7 @@ int main(int argc, char *argv[]) {
|
||||
|
||||
/* 注册信号处理 */
|
||||
cocoon_signal_setup(signal_handler);
|
||||
signal(SIGUSR1, reload_handler);
|
||||
|
||||
/* 设置日志级别 */
|
||||
log_set_level(config.log_level);
|
||||
|
||||
44
plugin.c
44
plugin.c
@ -26,6 +26,20 @@ typedef struct {
|
||||
|
||||
static plugin_entry_t g_plugins[MAX_PLUGINS];
|
||||
static size_t g_plugin_count = 0;
|
||||
/* 存储已加载的插件路径,用于热重载 */
|
||||
static char g_plugin_paths[MAX_PLUGINS][256];
|
||||
static size_t g_plugin_path_count = 0;
|
||||
|
||||
/**
|
||||
* plugin_store_path - 保存插件路径到重载列表
|
||||
*/
|
||||
static void plugin_store_path(const char *path) {
|
||||
if (g_plugin_path_count < MAX_PLUGINS) {
|
||||
strncpy(g_plugin_paths[g_plugin_path_count], path, sizeof(g_plugin_paths[0]) - 1);
|
||||
g_plugin_paths[g_plugin_path_count][sizeof(g_plugin_paths[0]) - 1] = '\0';
|
||||
g_plugin_path_count++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* cocoon_plugin_load - 加载一个插件
|
||||
@ -74,6 +88,9 @@ int cocoon_plugin_load(const char *path) {
|
||||
entry->path[sizeof(entry->path) - 1] = '\0';
|
||||
g_plugin_count++;
|
||||
|
||||
/* 同时保存路径用于重载 */
|
||||
plugin_store_path(path);
|
||||
|
||||
log_info("plugin: 已加载 %s (v%s, #%zu)", path, ver_str, g_plugin_count);
|
||||
return 0;
|
||||
}
|
||||
@ -94,6 +111,33 @@ void cocoon_plugin_unload_all(void) {
|
||||
g_plugin_count = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* cocoon_plugin_reload - 热重载所有插件
|
||||
*
|
||||
* 先卸载所有插件,再按存储路径重新加载。
|
||||
*/
|
||||
int cocoon_plugin_reload(void) {
|
||||
if (g_plugin_path_count == 0) {
|
||||
log_warn("plugin: 没有存储的插件路径,无法重载");
|
||||
return 0; /* 没有插件也算成功 */
|
||||
}
|
||||
|
||||
log_info("plugin: 开始热重载 %zu 个插件...", g_plugin_path_count);
|
||||
cocoon_plugin_unload_all();
|
||||
/* 保留路径,重新加载 */
|
||||
size_t success = 0;
|
||||
for (size_t i = 0; i < g_plugin_path_count; i++) {
|
||||
if (cocoon_plugin_load(g_plugin_paths[i]) == 0) {
|
||||
success++;
|
||||
} else {
|
||||
log_error("plugin: 重载失败 %s", g_plugin_paths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
log_info("plugin: 热重载完成,%zu/%zu 成功", success, g_plugin_path_count);
|
||||
return (success == g_plugin_path_count) ? 0 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* cocoon_plugin_count - 已加载插件数量
|
||||
*/
|
||||
|
||||
10
plugin.h
10
plugin.h
@ -68,6 +68,16 @@ int cocoon_plugin_load(const char *path);
|
||||
*/
|
||||
void cocoon_plugin_unload_all(void);
|
||||
|
||||
/**
|
||||
* cocoon_plugin_reload - 热重载所有插件
|
||||
*
|
||||
* 先卸载所有已加载插件,再按存储的路径重新加载。
|
||||
* 用于开发调试场景(通过 SIGUSR1 触发)。
|
||||
*
|
||||
* @return 0 成功,-1 失败
|
||||
*/
|
||||
int cocoon_plugin_reload(void);
|
||||
|
||||
/**
|
||||
* cocoon_plugin_count - 获取当前已加载的插件数量
|
||||
*/
|
||||
|
||||
@ -886,6 +886,30 @@ if gcc -Wall -Wextra -fPIC -shared -I. -Icoco/include -o "$TMPDIR/hello_plugin.s
|
||||
echo " ✗ 插件日志 — 未找到加载日志"
|
||||
fail
|
||||
fi
|
||||
|
||||
# 测试插件热重载
|
||||
PLUGIN_PID=$(pgrep -f "cocoon.*--plugin.*hello_plugin.so" 2>/dev/null || true)
|
||||
if [[ -n "$PLUGIN_PID" ]]; then
|
||||
kill -USR1 "$PLUGIN_PID" 2>/dev/null
|
||||
sleep 0.5
|
||||
plugin_http_after=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/")
|
||||
if [[ "$plugin_http_after" == "200" ]]; then
|
||||
echo " ✓ 插件热重载 — SIGUSR1 后服务器正常响应 HTTP 200"
|
||||
pass
|
||||
else
|
||||
echo " ✗ 插件热重载 — 期望 200, 实际 $plugin_http_after"
|
||||
fail
|
||||
fi
|
||||
if grep -q "热重载" "$TMPDIR/server_plugin.log"; then
|
||||
echo " ✓ 插件热重载日志 — 热重载日志正确输出"
|
||||
pass
|
||||
else
|
||||
echo " ✗ 插件热重载日志 — 未找到热重载日志"
|
||||
fail
|
||||
fi
|
||||
else
|
||||
echo " ⊘ 插件热重载 — 无法获取服务器 PID"
|
||||
fi
|
||||
else
|
||||
echo " ⊘ 插件编译跳过(无编译器)"
|
||||
fi
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user