W17: extract ai_common shared module + fix anthropic data race + brace bugs
- New plugins_upper/ai_common/ static library: shared PluginConfig, ToolCallAccum, StreamContext, secure_zero, extract_host_port, serialize_tool_calls, free_chat_result - Refactored openai/anthropic plugins to use dstalk_ai:: namespace from ai_common - Fixed anthropic g_config raw pointer → std::atomic (data race) - Added SSE parse error counter with threshold abort (kMaxSseParseErrors=5) - Fixed missing closing brace in both plugins' error-body catch block - Updated test targets: ai_common include path + link, using namespace dstalk_ai - plugin_loader_test: added stub_unreg + service_registry.cpp for unregister_service - Includes pre-existing uncommitted changes from prior waves Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -65,6 +65,7 @@ typedef struct {
|
||||
/* --- 服务注册表 / service registry --- */
|
||||
int (*register_service)(const char* name, int version, void* vtable);
|
||||
void*(*query_service)(const char* name, int min_version);
|
||||
void (*unregister_service)(const char* name);
|
||||
|
||||
/* --- 事件总线 / event bus --- */
|
||||
int (*event_subscribe)(int event_type, dstalk_event_handler_fn handler, void* userdata);
|
||||
|
||||
@@ -15,7 +15,7 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
/* ---- AI 服务 vtable / AI service vtable ---- */
|
||||
/* 以名称如 "ai.openai" 或 "ai.anthropic" 注册 / Registered under names such as "ai.openai" or "ai.anthropic" */
|
||||
/* 以名称如 "ai_openai" 或 "ai_anthropic" 注册 / Registered under names such as "ai_openai" or "ai_anthropic" */
|
||||
typedef struct {
|
||||
/* 配置服务商连接 (base_url, api_key, model 等) / Configure provider connection (base_url, api_key, model, etc.) */
|
||||
int (*configure)(const char* provider, const char* base_url,
|
||||
|
||||
@@ -94,6 +94,11 @@ namespace {
|
||||
return g_service_registry ? g_service_registry->query_service(name, min_version) : nullptr;
|
||||
}
|
||||
|
||||
// Unregister a service by name from the global registry (no-op if name is null).
|
||||
void api_unregister_service(const char* name) {
|
||||
if (g_service_registry) g_service_registry->unregister_service(name);
|
||||
}
|
||||
|
||||
// 通过全局事件总线订阅指定事件类型的处理函数。
|
||||
// Subscribe a handler to a given event type via the global event bus.
|
||||
int api_event_subscribe(int event_type, dstalk_event_handler_fn handler, void* userdata) {
|
||||
@@ -150,6 +155,7 @@ namespace {
|
||||
dstalk_host_api_t g_host_api = {
|
||||
api_register_service,
|
||||
api_query_service,
|
||||
api_unregister_service,
|
||||
api_event_subscribe,
|
||||
api_event_emit,
|
||||
api_event_unsubscribe,
|
||||
@@ -218,6 +224,9 @@ DSTALK_API int dstalk_init(const char* config_path)
|
||||
g_service_registry = new dstalk::ServiceRegistry();
|
||||
g_plugin_loader = new dstalk::PluginLoader();
|
||||
|
||||
// 连接 PluginLoader 和 ServiceRegistry,以便插件卸载时清理服务注册表 / Wire PluginLoader to ServiceRegistry for service cleanup on plugin unload
|
||||
g_plugin_loader->set_service_registry(g_service_registry);
|
||||
|
||||
// 加载配置 / Load config
|
||||
if (config_path && config_path[0]) {
|
||||
if (g_config->load_file(config_path) != 0) {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
|
||||
#include "plugin_loader.hpp"
|
||||
#include "service_registry.hpp"
|
||||
|
||||
#include <boost/json.hpp>
|
||||
|
||||
@@ -234,6 +235,17 @@ int PluginLoader::unload_plugin(int plugin_id)
|
||||
}
|
||||
}
|
||||
|
||||
// 清理该插件在服务注册表中的条目,避免 dll 卸载后 vtable 悬空指针 / Clean up service registry entries to avoid dangling vtable pointers after DLL unload
|
||||
if (service_registry_) {
|
||||
auto svc_it = plugin_services_.find(plugin.name);
|
||||
if (svc_it != plugin_services_.end()) {
|
||||
for (const auto& svc_name : svc_it->second) {
|
||||
service_registry_->unregister_service(svc_name.c_str());
|
||||
}
|
||||
plugin_services_.erase(svc_it);
|
||||
}
|
||||
}
|
||||
|
||||
// 卸载DLL / Unload DLL
|
||||
#ifdef _WIN32
|
||||
FreeLibrary((HMODULE)plugin.handle);
|
||||
@@ -422,6 +434,13 @@ int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
|
||||
if (plugin.info->on_init) {
|
||||
int result;
|
||||
|
||||
// 记录 on_init 前的服务名称快照,用于 on_init 后 diff 出该插件注册的服务 / Snapshot service names before on_init to discover newly registered services after
|
||||
std::vector<std::string> before_svcs;
|
||||
if (service_registry_) {
|
||||
before_svcs = service_registry_->list_service_names();
|
||||
}
|
||||
|
||||
try {
|
||||
result = plugin.info->on_init(host_api);
|
||||
} catch (const std::exception& e) {
|
||||
@@ -444,6 +463,17 @@ int PluginLoader::initialize_all(const dstalk_host_api_t* host_api)
|
||||
failed_count++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// 将 on_init 期间新注册的服务归因到当前插件 / Attribute newly registered services to this plugin
|
||||
if (service_registry_) {
|
||||
std::vector<std::string> after_svcs = service_registry_->list_service_names();
|
||||
std::unordered_set<std::string> before_set(before_svcs.begin(), before_svcs.end());
|
||||
for (const auto& svc_name : after_svcs) {
|
||||
if (before_set.find(svc_name) == before_set.end()) {
|
||||
plugin_services_[plugin.name].push_back(svc_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
plugin.initialized = true;
|
||||
}
|
||||
@@ -477,6 +507,13 @@ int PluginLoader::initialize_pending(const dstalk_host_api_t* host_api)
|
||||
|
||||
if (plugin.info->on_init) {
|
||||
int result;
|
||||
|
||||
// 记录 on_init 前的服务名称快照,用于 on_init 后 diff 出该插件注册的服务 / Snapshot service names before on_init to discover newly registered services after
|
||||
std::vector<std::string> before_svcs;
|
||||
if (service_registry_) {
|
||||
before_svcs = service_registry_->list_service_names();
|
||||
}
|
||||
|
||||
try {
|
||||
result = plugin.info->on_init(host_api);
|
||||
} catch (const std::exception& e) {
|
||||
@@ -491,6 +528,17 @@ int PluginLoader::initialize_pending(const dstalk_host_api_t* host_api)
|
||||
if (result != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 将 on_init 期间新注册的服务归因到当前插件 / Attribute newly registered services to this plugin
|
||||
if (service_registry_) {
|
||||
std::vector<std::string> after_svcs = service_registry_->list_service_names();
|
||||
std::unordered_set<std::string> before_set(before_svcs.begin(), before_svcs.end());
|
||||
for (const auto& svc_name : after_svcs) {
|
||||
if (before_set.find(svc_name) == before_set.end()) {
|
||||
plugin_services_[plugin.name].push_back(svc_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
plugin.initialized = true;
|
||||
count++;
|
||||
@@ -537,6 +585,12 @@ void PluginLoader::shutdown_all()
|
||||
plugin.initialized = false;
|
||||
}
|
||||
|
||||
// 清空服务注册表,避免后续 DLL 卸载后 vtable 悬空指针 / Clear the service registry to prevent dangling vtable pointers after DLL unload
|
||||
if (service_registry_) {
|
||||
service_registry_->clear();
|
||||
}
|
||||
plugin_services_.clear();
|
||||
|
||||
// 释放所有 DLL 句柄 / Free all DLL handles
|
||||
for (auto& [id, plugin] : plugins_) {
|
||||
if (plugin.handle) {
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
class ServiceRegistry;
|
||||
|
||||
// 描述单个已加载插件:标识、DLL 句柄、信息 vtable 和初始化状态。
|
||||
// Describes a single loaded plugin: identity, DLL handle, info vtable, and init state.
|
||||
struct PluginInfo {
|
||||
@@ -60,6 +63,9 @@ public:
|
||||
// 获取插件信息 / Get plugin info
|
||||
const PluginInfo* get_plugin(int plugin_id) const;
|
||||
|
||||
// 设置服务注册表引用,供卸载插件时清理服务 / Set service registry reference for service cleanup during plugin unload
|
||||
void set_service_registry(ServiceRegistry* sr) { service_registry_ = sr; }
|
||||
|
||||
private:
|
||||
// 拓扑排序(按依赖顺序) / Topological sort (by dependency order)
|
||||
std::vector<int> topological_sort() const;
|
||||
@@ -71,6 +77,10 @@ private:
|
||||
std::unordered_map<int, PluginInfo> plugins_;
|
||||
std::atomic<int> next_id_{1};
|
||||
const dstalk_host_api_t* host_api_ = nullptr;
|
||||
|
||||
ServiceRegistry* service_registry_ = nullptr; // 用于卸载时清理服务 / for service cleanup during unload
|
||||
// 插件名称 -> 该插件注册的服务名称列表 / plugin name -> list of service names registered by that plugin
|
||||
std::unordered_map<std::string, std::vector<std::string>> plugin_services_;
|
||||
};
|
||||
|
||||
} // namespace dstalk
|
||||
|
||||
@@ -48,4 +48,23 @@ void ServiceRegistry::unregister_service(const char* name)
|
||||
services_.erase(name);
|
||||
}
|
||||
|
||||
// 列出当前所有已注册服务名称(shared_lock)/ List all currently registered service names (shared_lock).
|
||||
std::vector<std::string> ServiceRegistry::list_service_names() const
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
std::vector<std::string> names;
|
||||
names.reserve(services_.size());
|
||||
for (const auto& [name, _] : services_) {
|
||||
names.push_back(name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
// 移除所有已注册服务(unique_lock)/ Remove all registered services (unique_lock).
|
||||
void ServiceRegistry::clear()
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
services_.clear();
|
||||
}
|
||||
|
||||
} // namespace dstalk
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace dstalk {
|
||||
|
||||
@@ -32,6 +33,12 @@ public:
|
||||
// 注销服务 / Unregister a named service
|
||||
void unregister_service(const char* name);
|
||||
|
||||
// 列出所有已注册服务名称(用于 diff/遍历)/ List all currently registered service names (for diff / iteration)
|
||||
std::vector<std::string> list_service_names() const;
|
||||
|
||||
// 清空所有注册服务 / Remove all registered services
|
||||
void clear();
|
||||
|
||||
private:
|
||||
struct ServiceEntry {
|
||||
std::string name;
|
||||
|
||||
Reference in New Issue
Block a user