feat(plugin): 插件热重载支持(SIGUSR1)

- plugin.c/plugin.h: 新增 cocoon_plugin_reload() 热重载 API
- 自动存储已加载插件路径,卸载后按原路径重新加载
- main.c: 注册 SIGUSR1 信号处理器,触发插件热重载
- 添加 --help 中 Signals 说明文档
- 集成测试:新增 2 项热重载测试(功能验证 + 日志验证)
- 全部 142 个单元测试 + 77 项集成测试通过
This commit is contained in:
xfy911 2026-06-06 15:11:17 +08:00
parent 4b647cd540
commit 80385d9657
4 changed files with 103 additions and 1 deletions

26
main.c
View File

@ -11,15 +11,22 @@
#include "config.h" #include "config.h"
#include "platform.h" #include "platform.h"
#include "access_log.h" #include "access_log.h"
#include "plugin.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <signal.h>
/** /**
* g_ctx - * g_ctx -
*/ */
static server_context_t *g_ctx = NULL; static server_context_t *g_ctx = NULL;
/**
* g_reload_plugins - SIGUSR1
*/
static volatile int g_reload_plugins = 0;
/** /**
* signal_handler - * 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 - 使 * print_usage - 使
* *
@ -62,7 +83,9 @@ static void print_usage(const char *prog) {
printf(" --auth-pass <pass> Basic Auth 密码\n"); printf(" --auth-pass <pass> Basic Auth 密码\n");
printf(" --rate-limit <n> 每秒最大请求数(限流)\n"); printf(" --rate-limit <n> 每秒最大请求数(限流)\n");
printf(" --plugin <path> 加载插件(可多次指定)\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 -c cocoon.json\n", prog);
printf(" %s -r ./www -p 8080\n", prog); printf(" %s -r ./www -p 8080\n", prog);
printf(" %s -c cocoon.json -p 9090 # 命令行覆盖配置文件的端口\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); cocoon_signal_setup(signal_handler);
signal(SIGUSR1, reload_handler);
/* 设置日志级别 */ /* 设置日志级别 */
log_set_level(config.log_level); log_set_level(config.log_level);

View File

@ -26,6 +26,20 @@ typedef struct {
static plugin_entry_t g_plugins[MAX_PLUGINS]; static plugin_entry_t g_plugins[MAX_PLUGINS];
static size_t g_plugin_count = 0; 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 - * cocoon_plugin_load -
@ -74,6 +88,9 @@ int cocoon_plugin_load(const char *path) {
entry->path[sizeof(entry->path) - 1] = '\0'; entry->path[sizeof(entry->path) - 1] = '\0';
g_plugin_count++; g_plugin_count++;
/* 同时保存路径用于重载 */
plugin_store_path(path);
log_info("plugin: 已加载 %s (v%s, #%zu)", path, ver_str, g_plugin_count); log_info("plugin: 已加载 %s (v%s, #%zu)", path, ver_str, g_plugin_count);
return 0; return 0;
} }
@ -94,6 +111,33 @@ void cocoon_plugin_unload_all(void) {
g_plugin_count = 0; 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 - * cocoon_plugin_count -
*/ */

View File

@ -68,6 +68,16 @@ int cocoon_plugin_load(const char *path);
*/ */
void cocoon_plugin_unload_all(void); void cocoon_plugin_unload_all(void);
/**
* cocoon_plugin_reload -
*
*
* SIGUSR1
*
* @return 0 -1
*/
int cocoon_plugin_reload(void);
/** /**
* cocoon_plugin_count - * cocoon_plugin_count -
*/ */

View File

@ -886,6 +886,30 @@ if gcc -Wall -Wextra -fPIC -shared -I. -Icoco/include -o "$TMPDIR/hello_plugin.s
echo " ✗ 插件日志 — 未找到加载日志" echo " ✗ 插件日志 — 未找到加载日志"
fail fail
fi 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 else
echo " ⊘ 插件编译跳过(无编译器)" echo " ⊘ 插件编译跳过(无编译器)"
fi fi