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

166 lines
4.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* plugin.c - 插件系统实现
*
* 使用 dlopen / dlsym 实现动态加载。
* 保持线程安全:加载/卸载在主线程完成。
*
* @author xfy
*/
#include "plugin.h"
#include "log.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
/* dlopen 需要 RTLD_NOW 和 RTLD_LOCAL */
#include <dlfcn.h>
/* === 插件条目 === */
typedef struct {
void *handle; /**< dlopen 句柄 */
char path[256]; /**< 插件路径 */
cocoon_plugin_shutdown_func_t shutdown; /**< 关闭函数 */
cocoon_plugin_version_func_t version; /**< 版本函数 */
} plugin_entry_t;
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 - 加载一个插件
*/
int cocoon_plugin_load(const char *path) {
if (!path || !path[0]) {
log_error("plugin: 空路径");
return -1;
}
if (g_plugin_count >= MAX_PLUGINS) {
log_error("plugin: 注册表已满(最多 %d 个)", MAX_PLUGINS);
return -1;
}
void *handle = dlopen(path, RTLD_NOW | RTLD_LOCAL);
if (!handle) {
log_error("plugin: 无法加载 %s: %s", path, dlerror());
return -1;
}
/* 查找符号 */
cocoon_plugin_init_func_t init = (cocoon_plugin_init_func_t)dlsym(handle, "cocoon_plugin_init");
cocoon_plugin_shutdown_func_t shutdown = (cocoon_plugin_shutdown_func_t)dlsym(handle, "cocoon_plugin_shutdown");
cocoon_plugin_version_func_t version = (cocoon_plugin_version_func_t)dlsym(handle, "cocoon_plugin_version");
const char *ver_str = version ? version() : "unknown";
/* 如果存在 init则调用 */
if (init) {
int ret = init();
if (ret != 0) {
log_error("plugin: %s 初始化失败(返回 %d已卸载", path, ret);
dlclose(handle);
return -1;
}
} else {
log_warn("plugin: %s 缺少 cocoon_plugin_init仅加载", path);
}
/* 保存到注册表 */
plugin_entry_t *entry = &g_plugins[g_plugin_count];
entry->handle = handle;
entry->shutdown = shutdown;
entry->version = version;
strncpy(entry->path, path, sizeof(entry->path) - 1);
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;
}
/**
* cocoon_plugin_unload_all - 卸载所有插件
*/
void cocoon_plugin_unload_all(void) {
/* 逆序卸载 */
for (size_t i = g_plugin_count; i > 0; i--) {
plugin_entry_t *entry = &g_plugins[i - 1];
if (entry->shutdown) {
entry->shutdown();
}
dlclose(entry->handle);
log_info("plugin: 已卸载 %s", entry->path);
}
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 - 已加载插件数量
*/
size_t cocoon_plugin_count(void) {
return g_plugin_count;
}
/**
* cocoon_plugin_get_version - 获取插件版本
*/
const char *cocoon_plugin_get_version(size_t index) {
if (index >= g_plugin_count) return "unknown";
if (g_plugins[index].version) {
return g_plugins[index].version();
}
return "unknown";
}
/**
* cocoon_plugin_get_path - 获取插件路径
*/
const char *cocoon_plugin_get_path(size_t index) {
if (index >= g_plugin_count) return NULL;
return g_plugins[index].path;
}