diff --git a/main.c b/main.c index a4f9e69..88c718d 100644 --- a/main.c +++ b/main.c @@ -11,15 +11,22 @@ #include "config.h" #include "platform.h" #include "access_log.h" +#include "plugin.h" #include #include #include +#include /** * 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 Basic Auth 密码\n"); printf(" --rate-limit 每秒最大请求数(限流)\n"); printf(" --plugin 加载插件(可多次指定)\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); diff --git a/plugin.c b/plugin.c index ab6c0c3..36b9419 100644 --- a/plugin.c +++ b/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 - 已加载插件数量 */ diff --git a/plugin.h b/plugin.h index 0a35b1b..5070253 100644 --- a/plugin.h +++ b/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 - 获取当前已加载的插件数量 */ diff --git a/tests/integration_test.sh b/tests/integration_test.sh index 7a9bafc..799f38b 100755 --- a/tests/integration_test.sh +++ b/tests/integration_test.sh @@ -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