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:
2026-05-31 16:58:25 +08:00
parent ba7382db2a
commit 8faa02c3d5
49 changed files with 1062 additions and 413 deletions

View File

@@ -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);

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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

View File

@@ -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;