From 2f51c16a873b3ee955e94a023c22d6c031d39b1d Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 09:25:50 +0800 Subject: [PATCH 01/13] refactor(consul)!: adopt typed plugin construction Replace Consul creation macros with typed bases, a private owning factory and explicit DLL entry points. Preserve interface registration, disabled updates and request behavior, and add construction/lifecycle checks with bilingual migration guidance. BREAKING CHANGE: Consul implementation and plugin class layouts change. Rebuild Consul and its consumers together; all existing public types and logical header paths remain available. --- docs/guides/plugins.md | 2 + docs/guides/plugins_CN.md | 2 + docs/project-layout.md | 14 ++++ docs/project-layout_CN.md | 14 ++++ docs/reference/build-options.md | 2 +- docs/reference/build-options_CN.md | 2 +- src/plugins/README.md | 4 +- src/plugins/README_CN.md | 4 +- src/plugins/consul/CMakeLists.txt | 18 +----- src/plugins/consul/consul_factory.hpp | 13 ++++ src/plugins/consul/consul_module_impl.cpp | 5 ++ src/plugins/consul/consul_module_impl.hpp | 5 +- src/plugins/consul/consul_plugin.cpp | 23 +++++-- src/plugins/consul/consul_plugin.hpp | 12 +++- tests/consul/CMakeLists.txt | 36 +++++++++++ tests/consul/construction.cpp | 53 +++++++++++++++ tests/consul/entry.cpp | 45 +++++++++++++ tests/consul/entry_factory.cpp | 79 +++++++++++++++++++++++ tests/consul/exports.cpp | 31 +++++++++ tests/core/CMakeLists.txt | 4 ++ 20 files changed, 337 insertions(+), 31 deletions(-) create mode 100644 src/plugins/consul/consul_factory.hpp create mode 100644 tests/consul/CMakeLists.txt create mode 100644 tests/consul/construction.cpp create mode 100644 tests/consul/entry.cpp create mode 100644 tests/consul/entry_factory.cpp create mode 100644 tests/consul/exports.cpp diff --git a/docs/guides/plugins.md b/docs/guides/plugins.md index 0403f25b5..9c9e8f5cf 100644 --- a/docs/guides/plugins.md +++ b/docs/guides/plugins.md @@ -211,3 +211,5 @@ ABI stability. Lifecycle phases, draining and library unload rules are unchanged Net constructs its HTTP client through a private factory returning `std::unique_ptr` and registers it with updates enabled. Applications consume `` and typed manager lookup; the old `http_client_module_impl.hpp` is removed from the public/install interfaces. Rebuild affected consumers together; see [Net migration](../reference/network.md#api-and-private-construction). Kernel also uses typed construction while retaining its public `*_impl` and `kernel_plugin` types and header paths. Its private factory header is not installed. Rebuild Kernel and consumers together because those public class layouts change; the [Kernel migration table](../reference/kernel.md#typed-kernel-construction) records unchanged interface keys, update selection and registration order. + +Consul follows the same construction pattern while retaining all three public headers and types. Its private factory is not installed; the module keeps its interface RTTI key and disabled updates. Rebuild Consul and consumers together because its public implementation/plugin layouts change. See [Consul construction migration](../project-layout.md#typed-consul-construction) for test entry points and dependency conditions. diff --git a/docs/guides/plugins_CN.md b/docs/guides/plugins_CN.md index 1458fc47d..39ba62f2a 100644 --- a/docs/guides/plugins_CN.md +++ b/docs/guides/plugins_CN.md @@ -198,3 +198,5 @@ module_update::enabled)` 启用 update;`disabled` 则不参与 update,即使 Net 通过私有工厂返回 `std::unique_ptr`,并启用该模块的 update 注册。应用使用 `` 和管理器的类型化查找;旧 `http_client_module_impl.hpp` 从公开/安装接口移除。受影响的消费者需要一起重建,详见 [Net 迁移](../reference/network_CN.md#api-与私有创建)。 Kernel 同样使用类型化构造,并保留公开 `*_impl`、`kernel_plugin` 类型与头路径;私有工厂头不安装。公开类布局发生变化,Kernel 与消费者需要一起重建。[Kernel 迁移表](../reference/kernel_CN.md#kernel-类型化构造)记录保持不变的接口键、update 选择与注册顺序。 + +Consul 使用相同构造方式,保留全部三个公开头与类型。私有工厂不安装;模块保留接口 RTTI 键和禁用 update 的选择。公开实现/插件布局发生变化,Consul 与消费者必须一起重建。测试入口与依赖条件见 [Consul 构造迁移](../project-layout_CN.md#consul-类型化构造)。 diff --git a/docs/project-layout.md b/docs/project-layout.md index 6ce9b31b3..79cff408b 100644 --- a/docs/project-layout.md +++ b/docs/project-layout.md @@ -134,3 +134,17 @@ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel ``` Header mapping does not establish Consul, Kafka or OSS SDK runtime compatibility. Physic and `physics_scene` still require the unsupported legacy PhysX 3 integration and retain their explicit configuration failure; no passing PhysX build is claimed. + +## Typed Consul construction + +**Breaking ABI/layout change:** `consul_module_impl` now inherits `module_base` and `consul_plugin` inherits `plugin_base`. Rebuild Consul and consumers together. All three public headers, their `` paths, public types and request methods remain available through `ark::consul`; the interface itself is unchanged. The C++ plugin boundary does not promise compiler-independent ABI compatibility. + +The private `consul_factory.hpp` returns `std::unique_ptr` and is excluded from public mappings and installation. Explicit `DllEntryPlugin` and `DllExitPlugin` retain the existing platform exports and logger setup. Installation registers the single `consul_module` interface RTTI key with `module_update::disabled`; uninstallation removes and destroys that module. The implementation still inherits the default update method. Configuration storage, HTTP-client lookup, TOML loading, request serialization and endpoints are unchanged. + +With `ARK_BUILD_CORE_TESTS=ON`, `ARK_BUILD_PROTO=ON` and `ARK_BUILD_CONFIG=ON`, `check-consul-construction` runs the actual factory/assembly doctest cases and a controlled-factory DSO test of the actual entry/exit source. These cover public bases, factory metadata, interface registration, duplicate rejection, disabled updates, configuration handoff, ownership and repeated stop. They require Protobuf, toml++ and the normal core/arknet dependencies, but no runtime plugin or Consul service. For an existing build configured with those options: + +```sh +cmake --build build --target check-consul-construction --parallel 2 +``` + +`ARK_BUILD_PLUGIN_CONSUL=ON` also requires `ARK_BUILD_APPLICATIONS=ON` and remains off by default. It links the generated protocol target `ark::proto` directly. With the core tests enabled, `check-consul-plugin` additionally inspects the actual Consul DSO exports in a separate process; `check-core` includes the applicable Consul checks. The controlled factory does not exercise the production HTTP client or a Consul server. These construction checks do not establish live Consul interoperability, protocol acceptance or arbitrary hot-update safety. diff --git a/docs/project-layout_CN.md b/docs/project-layout_CN.md index 82a8ee230..a251e8d4e 100644 --- a/docs/project-layout_CN.md +++ b/docs/project-layout_CN.md @@ -124,3 +124,17 @@ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel ``` 头文件映射检查不代表 Consul、Kafka、OSS SDK 的运行时兼容性已经验证。Physic 与 `physics_scene` 仍依赖未受支持的旧版 PhysX 3 集成,保留明确的配置失败;此处没有宣称 PhysX 构建通过。 + +## Consul 类型化构造 + +**ABI/布局不兼容变更:** `consul_module_impl` 现在继承 `module_base`,`consul_plugin` 继承 `plugin_base`。Consul 与消费者必须一起重建。原有三个公开头、`` 路径、公开类型与请求方法继续通过 `ark::consul` 提供,接口本身不变。C++ 插件边界不承诺跨编译器的 ABI 兼容性。 + +私有 `consul_factory.hpp` 返回 `std::unique_ptr`,不映射、不安装。显式 `DllEntryPlugin` 与 `DllExitPlugin` 保留原有平台导出和日志初始化。安装时按 `consul_module` 接口 RTTI 键注册唯一模块,显式使用 `module_update::disabled`;卸载时移除并销毁该模块。实现仍继承默认 update 方法。配置存储、HTTP 客户端查找、TOML 加载、请求序列化及端点保持不变。 + +开启 `ARK_BUILD_CORE_TESTS=ON`、`ARK_BUILD_PROTO=ON` 与 `ARK_BUILD_CONFIG=ON` 后,`check-consul-construction` 运行真实工厂/装配的 doctest 用例,以及使用受控工厂与真实入口/出口源码的 DSO 测试。检查覆盖公开基类、工厂元数据、接口注册、重复注册拒绝、禁用 update、配置传递、所有权与重复 stop。依赖 Protobuf、toml++ 及通常的 core/arknet 依赖,无需运行时插件或 Consul 服务。对于已按上述选项配置的构建: + +```sh +cmake --build build --target check-consul-construction --parallel 2 +``` + +`ARK_BUILD_PLUGIN_CONSUL=ON` 还要求 `ARK_BUILD_APPLICATIONS=ON`,默认保持关闭。插件直接链接生成协议目标 `ark::proto`。开启核心测试后,`check-consul-plugin` 还在独立进程中检查真实 Consul DSO 的导出符号;`check-core` 包含当前条件下可用的 Consul 检查。受控工厂不执行生产 HTTP 客户端或访问 Consul 服务。这些构造检查不代表真实 Consul 互通、协议验收或任意热更新安全。 diff --git a/docs/reference/build-options.md b/docs/reference/build-options.md index bf0875f98..52cd11ef9 100644 --- a/docs/reference/build-options.md +++ b/docs/reference/build-options.md @@ -39,7 +39,7 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction) changes public class layouts and requires rebuilding consumers. Its private factory header is excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. +All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction) and [Consul typed construction](../project-layout.md#typed-consul-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. Link a consumer to `ark::mysql` and/or `ark::redis` for the public headers and usage requirements. These public interface targets do not load a runtime library. Deploy the selected libraries with the launcher and add their stems to the application's manifest: diff --git a/docs/reference/build-options_CN.md b/docs/reference/build-options_CN.md index da133ef1b..447194a30 100644 --- a/docs/reference/build-options_CN.md +++ b/docs/reference/build-options_CN.md @@ -39,7 +39,7 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)改变公开类布局,需要重建消费者。其私有工厂头不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 +全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)与 [Consul 类型化构造](../project-layout_CN.md#consul-类型化构造)改变公开类布局,需要重建消费者。两者的私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 业务插件链接 `ark::mysql` 和/或 `ark::redis`,以获取公开头文件和编译要求;公共接口目标不会加载运行时动态库。将所需动态库与启动器一起部署,并在应用清单中填写库名称: diff --git a/src/plugins/README.md b/src/plugins/README.md index 07bbf6720..66e429e98 100755 --- a/src/plugins/README.md +++ b/src/plugins/README.md @@ -26,4 +26,6 @@ See [project layout and public plugin interfaces](../../docs/project-layout.md) Kernel co-locates its 38 existing headers with its cpp files in `src/plugins/kernel/`. `ark::kernel` preserves every `` path, including `data_impl.hpp`, `data_list_impl.hpp` and `kernel_plugin.hpp`. The retained public implementation/plugin types use typed construction with private owning factories and explicit entry points. `kernel_factory.hpp` is neither mapped nor installed. The public class layouts change, so rebuild Kernel and consumers together; registration keys, update selection and lifecycle order are preserved. See [Kernel construction migration](../../docs/reference/kernel.md#typed-kernel-construction) for compatibility boundaries, consumption checks and Protobuf requirements. -The five remaining plugin families retain 22 byte-identical headers. Mapping checks do not validate Consul, Kafka or OSS SDK runtime integrations. Physic retains the unsupported PhysX 3 configuration failure; see [optional legacy dependencies](../../docs/dependencies.md#optional-legacy-integrations). +Consul retains its three public headers and implementation/plugin types while using typed construction, a private owning factory and explicit entry points. Rebuild Consul and consumers together because the public class layouts change. The single interface registration key, disabled updates and request behavior are preserved. See [Consul construction migration](../../docs/project-layout.md#typed-consul-construction) for build conditions and service-free construction checks. + +The other four legacy plugin families retain 19 byte-identical headers. Mapping checks do not validate Consul, Kafka or OSS SDK runtime integrations. Physic retains the unsupported PhysX 3 configuration failure; see [optional legacy dependencies](../../docs/dependencies.md#optional-legacy-integrations). diff --git a/src/plugins/README_CN.md b/src/plugins/README_CN.md index 188679dc2..82c42abd5 100644 --- a/src/plugins/README_CN.md +++ b/src/plugins/README_CN.md @@ -26,4 +26,6 @@ CMake target 和构建命令见[项目布局与公共插件接口](../../docs/pr Kernel 的原有 38 个头与 cpp 共置于 `src/plugins/kernel/`,通过 `ark::kernel` 保留全部 `` 路径,包括 `data_impl.hpp`、`data_list_impl.hpp` 和 `kernel_plugin.hpp`。保留的公开实现类型与插件类型使用类型化构造、私有拥有型工厂和显式入口。`kernel_factory.hpp` 不映射、不安装。公开类布局发生变化,Kernel 与消费者必须一起重建;注册键、update 选择和生命周期顺序保持不变。兼容边界、消费检查及 Protobuf 条件见 [Kernel 构造迁移](../../docs/reference/kernel_CN.md#kernel-类型化构造)。 -其余五类插件保留原有 22 个头的全部字节。映射检查不代表 Consul、Kafka、OSS SDK 的运行时集成已经验证。Physic 保留未受支持的 PhysX 3 配置失败;参见[可选旧集成依赖](../../docs/dependencies_CN.md)。 +Consul 保留原有三个公开头及实现/插件类型,同时使用类型化构造、私有拥有型工厂和显式入口。公开类布局发生变化,Consul 与消费者必须一起重建。唯一接口注册键、禁用 update 与请求行为保持不变。构建条件及无需服务的构造检查见 [Consul 构造迁移](../../docs/project-layout_CN.md#consul-类型化构造)。 + +其他四类旧插件保留原有 19 个头的全部字节。映射检查不代表 Consul、Kafka、OSS SDK 的运行时集成已经验证。Physic 保留未受支持的 PhysX 3 配置失败;参见[可选旧集成依赖](../../docs/dependencies_CN.md)。 diff --git a/src/plugins/consul/CMakeLists.txt b/src/plugins/consul/CMakeLists.txt index df78a2209..20b422335 100755 --- a/src/plugins/consul/CMakeLists.txt +++ b/src/plugins/consul/CMakeLists.txt @@ -1,17 +1 @@ -if(WIN32) - BUILD_PLUGIN_MACRO(ark_consul_plugin - debug AFProto_d.lib - optimized AFProto.lib - ${PROTOBUF_LIBRARIES}) -elseif(APPLE) - BUILD_PLUGIN_MACRO(ark_consul_plugin - debug ${BIN_OUTPUT_DIR}/AFProto_d.a - optimized ${BIN_OUTPUT_DIR}/AFProto.a - ${PROTOBUF_LIBRARIES}) -else(UNIX) - BUILD_PLUGIN_MACRO(ark_consul_plugin - debug :AFProto_d.a - optimized :AFProto.a - ${PROTOBUF_LIBRARIES}) -endif() -set_property(TARGET ark_consul_plugin APPEND PROPERTY LINK_LIBRARIES ark::config tomlplusplus::tomlplusplus) +BUILD_PLUGIN_MACRO(ark_consul_plugin ark::proto ark::config tomlplusplus::tomlplusplus) diff --git a/src/plugins/consul/consul_factory.hpp b/src/plugins/consul/consul_factory.hpp new file mode 100644 index 000000000..d65b95c48 --- /dev/null +++ b/src/plugins/consul/consul_factory.hpp @@ -0,0 +1,13 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "ark/consul/consul_module.hpp" + +// Plugin-private construction; deliberately absent from public header mappings. +namespace ark::detail { + +std::unique_ptr make_consul_module(); + +} // namespace ark::detail diff --git a/src/plugins/consul/consul_module_impl.cpp b/src/plugins/consul/consul_module_impl.cpp index df4cc4b1a..65f8f07fb 100644 --- a/src/plugins/consul/consul_module_impl.cpp +++ b/src/plugins/consul/consul_module_impl.cpp @@ -3,9 +3,14 @@ #include "../detail/plugin_configuration.hpp" #include "ark/consul/consul_module_impl.hpp" #include "ark/consul/consul_plugin.hpp" +#include "consul_factory.hpp" namespace ark { +std::unique_ptr detail::make_consul_module() { + return std::make_unique(); +} + namespace { std::future> failed_request() { diff --git a/src/plugins/consul/consul_module_impl.hpp b/src/plugins/consul/consul_module_impl.hpp index b2cb20c0b..be2adec8c 100644 --- a/src/plugins/consul/consul_module_impl.hpp +++ b/src/plugins/consul/consul_module_impl.hpp @@ -1,14 +1,13 @@ #pragma once #include "ark/consul/consul_module.hpp" +#include "ark/module_base.hpp" #include "ark/net/http_client_module.hpp" -#include "ark/plugin_manager.hpp" namespace ark { -class consul_module_impl final : public consul_module +class consul_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: bool init() override; diff --git a/src/plugins/consul/consul_plugin.cpp b/src/plugins/consul/consul_plugin.cpp index 1327a4d31..c6feb77ce 100644 --- a/src/plugins/consul/consul_plugin.cpp +++ b/src/plugins/consul/consul_plugin.cpp @@ -1,16 +1,31 @@ -#include "ark/consul/consul_module_impl.hpp" +#include + #include "ark/consul/consul_plugin.hpp" +#include "consul_factory.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(consul_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void consul_plugin::install() { - ARK_REGISTER_MODULE(consul_module, consul_module_impl); + if (!register_module(detail::make_consul_module(), module_update::disabled)) + { + throw std::runtime_error("Consul module registration rejected"); + } } void consul_plugin::uninstall() { - ARK_UNREGISTER_MODULE(consul_module, consul_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("Consul module unregistration rejected"); + } } } // namespace ark diff --git a/src/plugins/consul/consul_plugin.hpp b/src/plugins/consul/consul_plugin.hpp index d8b93aa2b..2aadec022 100755 --- a/src/plugins/consul/consul_plugin.hpp +++ b/src/plugins/consul/consul_plugin.hpp @@ -1,8 +1,14 @@ -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#pragma once + +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(consul_plugin) +class consul_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/tests/consul/CMakeLists.txt b/tests/consul/CMakeLists.txt new file mode 100644 index 000000000..fc45c3e15 --- /dev/null +++ b/tests/consul/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +set(consul_source "${PROJECT_SOURCE_DIR}/src/plugins/consul") + +# Compile real assembly/factory locally too: private C++ symbols are not DLL exports. +add_executable(ark_consul_construction_tests ../core/main.cpp construction.cpp + "${consul_source}/consul_plugin.cpp" "${consul_source}/consul_module_impl.cpp") +set_property(TARGET ark_consul_construction_tests PROPERTY LINK_LIBRARIES + ark::consul ark::proto ark::config tomlplusplus::tomlplusplus) +target_include_directories(ark_consul_construction_tests PRIVATE "${consul_source}") + +# A controlled module exercises the actual entry/exit functions without HTTP or Consul services. +add_library(ark_consul_entry_probe SHARED entry_factory.cpp "${consul_source}/consul_plugin.cpp") +set_property(TARGET ark_consul_entry_probe PROPERTY LINK_LIBRARIES ark::consul ark::proto) +target_include_directories(ark_consul_entry_probe PRIVATE "${consul_source}") +set_target_properties(ark_consul_entry_probe PROPERTIES PREFIX "") +add_executable(ark_consul_entry_tests entry.cpp) +set_property(TARGET ark_consul_entry_tests PROPERTY LINK_LIBRARIES ark::consul ark::proto) + +add_custom_target(check-consul-construction + COMMAND $ + COMMAND $ "$/" "ark_consul_entry_probe" + DEPENDS ark_consul_construction_tests ark_consul_entry_tests ark_consul_entry_probe + USES_TERMINAL VERBATIM) + +if(TARGET ark_consul_plugin) + # Keep real DSO export inspection separate from the host linking static AFProto. + add_executable(ark_consul_exports_tests exports.cpp) + set_property(TARGET ark_consul_exports_tests PROPERTY LINK_LIBRARIES ark::core) + add_custom_target(check-consul-plugin + COMMAND $ "$/" "ark_consul_plugin" + DEPENDS ark_consul_exports_tests ark_consul_plugin check-consul-construction + USES_TERMINAL VERBATIM) + add_dependencies(check-core check-consul-plugin) +else() + add_dependencies(check-core check-consul-construction) +endif() diff --git a/tests/consul/construction.cpp b/tests/consul/construction.cpp new file mode 100644 index 000000000..c466d65d7 --- /dev/null +++ b/tests/consul/construction.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include "../network/doctest.h" + +#include +#include +#include "consul_factory.hpp" + +#if __has_include() +#error "Consul public mappings must exclude the private factory" +#endif + +static_assert(std::is_base_of_v, ark::consul_module_impl>); +static_assert(std::is_base_of_v, ark::consul_plugin>); +static_assert(std::is_same_v); + +TEST_CASE("Consul real factory retains its public implementation and unpublished metadata") { + auto candidate = ark::detail::make_consul_module(); + REQUIRE(candidate != nullptr); + CHECK(typeid(*candidate) == typeid(ark::consul_module_impl)); + CHECK(candidate->get_plugin_manager() == nullptr); + CHECK(candidate->get_name().empty()); +} + +TEST_CASE("Consul assembly retains the interface key, disabled updates and explicit ownership") { + ark::plugin_manager manager; + ark::consul_plugin owner; + owner.set_plugin_manager(&manager); + CHECK(owner.get_plugin_version() == 0); + CHECK(owner.get_plugin_name() == typeid(ark::consul_plugin).name()); + owner.set_plugin_conf("consul-construction.toml"); + CHECK(ark::consul_plugin::get_plugin_conf() == "consul-construction.toml"); + owner.install(); + auto* instance = manager.find_module(); + REQUIRE(instance != nullptr); + CHECK(typeid(*instance) == typeid(ark::consul_module_impl)); + CHECK(instance->get_plugin_manager() == &manager); + CHECK(instance->get_name() == typeid(ark::consul_module).name()); + // Insertion succeeds only when assembly left the module out of the update set. + CHECK(manager.add_update_module(instance)); + manager.remove_update_module(instance->get_name()); + CHECK_THROWS_AS(owner.install(), std::runtime_error); + CHECK(manager.find_module() == instance); + owner.uninstall(); + // Reinstallation proves the public registration key and ownership were released. + CHECK_NOTHROW(owner.install()); + CHECK_NOTHROW(owner.uninstall()); + CHECK(manager.update(1)); + CHECK_THROWS_AS(owner.uninstall(), std::runtime_error); +} diff --git a/tests/consul/entry.cpp b/tests/consul/entry.cpp new file mode 100644 index 000000000..597dd1244 --- /dev/null +++ b/tests/consul/entry.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include +#include +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected Consul entry-probe directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load Consul entry-probe library"); + auto events = reinterpret_cast(library.get_symbol("consul_entry_events")); + require(events && events(0) == 0 && events(1) == 0, "Entry-probe state is not fresh"); + require(library.get_symbol("DllEntryPlugin") && library.get_symbol("DllExitPlugin"), + "Consul explicit entry/exit exports are missing"); + ark::plugin_manager manager; + require(manager.start(0, argv[1], "conf/", {{argv[2], "entry-probe.toml"}}), "Consul explicit entry failed"); + auto* instance = manager.find_module(); + require(instance && instance->get_plugin_manager() == &manager && + instance->get_name() == typeid(ark::consul_module).name(), + "Consul explicit entry lost interface registration metadata"); + require(events(0) == 1 && events(1) == 0, "Consul entry did not transfer factory ownership"); + require(manager.update(1) && events(2) == 0, "Consul entry changed disabled update selection"); + require(manager.stop(2) && events(1) == 1, "Consul explicit exit did not destroy its module exactly once"); + require(manager.stop(3) && events(1) == 1, "Repeated stop invoked Consul exit twice"); + require(library.unload(), "Cannot release Consul observation handle"); + std::cout << "Consul real assembly entry/exit, configuration, interface metadata and ownership passed\n"; +} diff --git a/tests/consul/entry_factory.cpp b/tests/consul/entry_factory.cpp new file mode 100644 index 000000000..d9f92b51b --- /dev/null +++ b/tests/consul/entry_factory.cpp @@ -0,0 +1,79 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include + +#include +#include +#include "consul_factory.hpp" + +namespace { + +unsigned created{}; +unsigned destroyed{}; +unsigned updated{}; + +class entry_consul final : public ark::module_base +{ +public: + entry_consul() { + ++created; + } + + ~entry_consul() override { + ++destroyed; + } + + bool init() override { + return ark::consul_plugin::get_plugin_conf() == "conf/entry-probe.toml"; + } + + bool update() override { + ++updated; + return true; + } + + std::future> register_service(const ark::consul::service_data&) override { + return {}; + } + + std::future> deregister_service(const std::string&) override { + return {}; + } + + std::future> get_health_services(const std::string&, const std::string&) override { + return {}; + } + + std::future> get_key_value(const std::string&) override { + return {}; + } + + std::future> set_key_value(const std::string&, const std::string&) override { + return {}; + } + + std::future> del_key_value(const std::string&) override { + return {}; + } +}; + +} // namespace + +namespace ark::detail { + +std::unique_ptr make_consul_module() { + return std::make_unique(); +} + +} // namespace ark::detail + +ARK_EXPORT_FUNC unsigned consul_entry_events(unsigned event) { + switch (event) + { + case 0: + return created; + case 1: + return destroyed; + default: + return updated; + } +} diff --git a/tests/consul/exports.cpp b/tests/consul/exports.cpp new file mode 100644 index 000000000..a5cbd76dc --- /dev/null +++ b/tests/consul/exports.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected plugin directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load actual Consul shared library"); + require(library.get_symbol("DllEntryPlugin") != nullptr, "Missing explicit DllEntryPlugin export"); + require(library.get_symbol("DllExitPlugin") != nullptr, "Missing explicit DllExitPlugin export"); + require(library.unload(), "Cannot unload Consul shared library"); + std::cout << argv[2] << ": actual DSO load, explicit entry/exit exports and unload passed\n"; +} diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index e027b5fe2..ae1f7a9e2 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -222,6 +222,10 @@ if(ARK_BUILD_PROTO) add_dependencies(check-core check-kernel-initialization) endif() +if(ARK_BUILD_PROTO AND ARK_BUILD_CONFIG) + add_subdirectory(../consul "${CMAKE_CURRENT_BINARY_DIR}/consul") +endif() + if(ARK_BUILD_PROTO AND TARGET ark_kernel_plugin AND TARGET ark_utility_plugin) add_executable(ark_kernel_plugin_tests ../app/kernel_plugin.cpp) set_property(TARGET ark_kernel_plugin_tests PROPERTY LINK_LIBRARIES ark::kernel) From fc9035a0d68d3b52d19377e8777435d4471067d8 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 09:30:13 +0800 Subject: [PATCH 02/13] fix(hash)!: standardize Murmur digests and retain legacy MSVC access Read hash blocks and write digest bytes without alignment or aliasing assumptions. Use C++20 rotations and provide explicit legacy MSVC methods for persisted-key migration. BREAKING CHANGE: default MurmurHash3 now uses canonical little-endian blocks and output. Old MSVC IDs, shard mappings and persisted keys require an explicit migration or the matching legacy MSVC method. --- docs/reference/utility.md | 76 ++++++++++ docs/reference/utility_CN.md | 57 ++++++++ src/core/hash/murmur_hash.hpp | 168 +++++++++++++++-------- tests/core/CMakeLists.txt | 2 +- tests/core/README.md | 10 ++ tests/core/README_CN.md | 8 ++ tests/core/murmur_hash.cpp | 251 ++++++++++++++++++++++++++++++++++ 7 files changed, 514 insertions(+), 58 deletions(-) create mode 100644 tests/core/murmur_hash.cpp diff --git a/docs/reference/utility.md b/docs/reference/utility.md index f468f5e16..22f3f5146 100644 --- a/docs/reference/utility.md +++ b/docs/reference/utility.md @@ -64,6 +64,82 @@ Public headers now have their single source in `src/plugins/utility/`. CMake cop Range selection retains the existing modulo mapping, seed sequence and draw count. Each call with different endpoints consumes one draw; reversing the endpoints produces the same sequence from the same seed. The unsigned overloads retain their existing behavior. +## Core Murmur hashes + +`` provides MurmurHash2 and the x86-32, x86-128 and +x64-128 MurmurHash3 variants through `ark::core`. Supply a nonnegative `int` +length, a readable input buffer (also for an empty input), and writable output +storage of 4 bytes for 32-bit variants or 16 bytes for 128-bit variants. Input +and output may have any byte alignment; word loads and stores use `std::memcpy`. +MurmurHash3 uses C++20 `std::rotl` with the original rotation counts. Seeds, +tail mixing, result word order and the existing public signatures are unchanged; +`murmur_hash3_x86_128` remains an instance method, and the other variants remain +static. MurmurHash2 retains its fixed seed zero. + +### Digest compatibility and legacy access + +**Breaking change:** default MurmurHash3 now reads full blocks as little endian +and writes canonical little-endian digest bytes on every supported platform. +This retains existing little-endian GCC/Clang results and changes the former +ordinary MSVC full-block policy. The former platform detection did not select +MSVC's `_M_IX86`, `_M_X64` or `_M_ARM64` macros +([Microsoft predefined macros](https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros)); +it fell through to an unconditional byte swap on a little-endian host. +The public `BYTESWAP32` and `BYTESWAP64` macros remain available, but no longer +control these hash methods. Callers must rebuild to use the new header behavior. + +| Method family | Default result | Previous ordinary MSVC result | +| --- | --- | --- | +| MurmurHash3 x86-32 | `murmur_hash3_x86_32` | `murmur_hash3_x86_32_legacy_msvc` | +| MurmurHash3 x86-128 | `murmur_hash3_x86_128` (instance method) | `murmur_hash3_x86_128_legacy_msvc` (instance method) | +| MurmurHash3 x64-128 | `murmur_hash3_x64_128` | `murmur_hash3_x64_128_legacy_msvc` | +| MurmurHash2 | `murmur_hash2`; unchanged on little-endian platforms | Same method; no separate legacy route is needed | + +The three `_legacy_msvc` methods reproduce the old MSVC result on any supported +platform: full blocks are read as big endian, tails are assembled as little +endian, and output words are serialized as little endian. Pass the original +variant, exact input bytes, length and seed. The policies agree for inputs shorter +than a full block (4 bytes for x86-32, 16 bytes for either 128-bit variant). +For bytes `00 01 02 03` and seed zero, x86-32 produces numeric word `0xf4c0ec39` +by default and `0x2cb313f3` through the legacy entry. A previous big-endian +consumer must also account for the change from native-endian output to canonical +little-endian bytes; `_legacy_msvc` specifically reproduces old MSVC behavior. + +### Migrate stored keys and routing + +Identify each dataset's hash variant, seed, input encoding and previous compiler +policy before upgrading. Keep that policy with the data or in an explicit schema +version. A digest alone cannot reconstruct the original key. IDs, shard mappings, +cache keys and persisted indexes derived from old MSVC hashes require an explicit +migration; replacing the function and silently recomputing routing is unsafe. + +For lookup keys, use a staged **dual-read / new-write** rollout: write new records +with the standard policy and a version tag, read that key first, and fall back to +the matching `_legacy_msvc` key for older records. Compare the original logical +key when resolving a hash hit or collision. Backfill or move old entries using +transactional/atomic application rules, define duplicate handling, and retain +legacy reads until inventory and rollback requirements permit their removal. +Old readers must understand new records before new-only writes begin. The library +does not migrate application records automatically. + +For consistent hashing or shard ownership, keep all routers on one explicit ring +version while planning the transition. Compute the new ring from authoritative +node identities, compare placement, copy/rebalance affected data, and coordinate +the routing epoch and ownership cutover across participants. Keep the legacy +ring and legacy hash entry until that transfer is accepted. Mixed old/new rings +during an ordinary rolling binary update can route the same key differently; +a dual lookup alone does not migrate ownership. + +`check-core` includes the [upstream SMHasher verification values](https://github.com/aappleby/smhasher/blob/master/src/main.cpp#L67-L70) +with its [prefix/seed procedure](https://github.com/aappleby/smhasher/blob/master/src/KeysetTest.cpp#L11-L38), +plus separate scalar-reference fixtures for the legacy MSVC policy. Cases cover +all seven public methods, every MurmurHash3 tail length, full-width seeds, +high-bit input bytes and rotation paths, MurmurHash2 boundary vectors, and +exact-size byte buffers at offsets 0 through 7. AddressSanitizer can detect +allocation-end overreads; UndefinedBehaviorSanitizer can detect invalid alignment. +The fixtures establish hash behavior; application data migration needs its own +acceptance evidence. + ## Verification `check-typed-examples` loads the actual Utility DSO with `timers`, `module_lifecycle` and `entity_operations`. It verifies all four interface RTTI keys and manager ownership, GUID generation/parsing, zlib/Zstandard binary round trips, a timer triggered by owner-thread time advancement, and normal/idempotent shutdown. `check-app-presets` exercises the shipped launcher preset; `check-core` includes both when applications, examples, Kernel and Utility are enabled. The independent `tests/consumer` project's `check-sdk` compiles public/helper paths with runtime plugins disabled and rejects visible implementation or assembly headers. These are correctness checks, not performance or arbitrary callback-lifetime acceptance. diff --git a/docs/reference/utility_CN.md b/docs/reference/utility_CN.md index 933c8f645..f9262cc25 100644 --- a/docs/reference/utility_CN.md +++ b/docs/reference/utility_CN.md @@ -58,6 +58,63 @@ zlib、LZ4 和 Zstandard 普通调用不再共享可写输出缓冲区。Zstanda 范围选择保留现有的取模映射、种子序列和生成次数。端点不同的每次调用消耗一个随机数;使用相同种子并交换端点,得到相同序列。无符号重载的行为保持不变。 +## Core Murmur 哈希 + +`` 通过 `ark::core` 提供 MurmurHash2,以及 MurmurHash3 +的 x86-32、x86-128 和 x64-128 变体。调用方须提供非负 `int` 长度、可读输入缓冲区 +(空输入也需要有效缓冲区),以及可写输出存储:32 位变体需要 4 字节,128 位变体需要 +16 字节。输入和输出允许任意字节对齐,字读取和写入使用 `std::memcpy`。 +MurmurHash3 使用 C++20 `std::rotl`,旋转次数保持不变。种子、尾部混合、结果字顺序和 +原有公开签名不变;`murmur_hash3_x86_128` 仍为实例方法,其余变体仍为静态方法。 +MurmurHash2 保留固定种子零。 + +### 摘要兼容性与旧结果入口 + +**不兼容变更:** 默认 MurmurHash3 现在在所有支持的平台上按小端方式读取完整块, +并输出规范的小端摘要字节。这保留原有小端 GCC/Clang 结果,改变普通 MSVC 的旧版完整块策略。 +旧版平台检测未选择 MSVC 的 `_M_IX86`、`_M_X64` 或 `_M_ARM64` 宏 +(见 [Microsoft 预定义宏](https://learn.microsoft.com/en-us/cpp/preprocessor/predefined-macros)), +导致小端主机进入无条件字节交换回退分支。公开宏 `BYTESWAP32` 和 `BYTESWAP64` +仍然保留,但不再控制这些哈希方法。调用方须重新编译,才能使用新的头文件行为。 + +| 方法族 | 默认结果 | 普通 MSVC 的旧结果 | +| --- | --- | --- | +| MurmurHash3 x86-32 | `murmur_hash3_x86_32` | `murmur_hash3_x86_32_legacy_msvc` | +| MurmurHash3 x86-128 | `murmur_hash3_x86_128`(实例方法) | `murmur_hash3_x86_128_legacy_msvc`(实例方法) | +| MurmurHash3 x64-128 | `murmur_hash3_x64_128` | `murmur_hash3_x64_128_legacy_msvc` | +| MurmurHash2 | `murmur_hash2`;在小端平台上保持不变 | 使用同一方法,无需独立旧版入口 | + +三个 `_legacy_msvc` 方法在所有支持的平台上复现旧 MSVC 结果:完整块按大端方式读取, +尾部按小端方式组装,输出字按小端方式序列化。调用时须使用原有变体、准确输入字节、 +长度和种子。输入短于完整块时,两种策略结果相同:x86-32 为不足 4 字节, +两个 128 位变体为不足 16 字节。对于字节 `00 01 02 03` 和种子零,x86-32 默认产生 +数值字 `0xf4c0ec39`,旧版入口产生 `0x2cb313f3`。旧版大端使用方还须处理输出从 +原生字节序变为规范小端字节序的变化;`_legacy_msvc` 专用于复现旧 MSVC 行为。 + +### 迁移已存储键与路由 + +升级前,先确认每个数据集的哈希变体、种子、输入编码和旧编译器策略,并在数据中或 +显式 schema 版本中记录该策略。单独的摘要无法还原原始键。由旧 MSVC 哈希派生的 ID、 +分片映射、缓存键和持久化索引需要显式迁移,不能替换函数后直接重算路由。 + +对于查找键,分阶段采用**双读、新写**:新记录使用标准策略并携带版本标记;读取时 +先查新键,未命中再查匹配的 `_legacy_msvc` 旧键。解析命中或哈希冲突时,核对原始逻辑键。 +按应用的事务或原子操作规则回填或移动旧记录,明确重复记录的处理方式;确认存量清理和 +回滚要求后,再移除旧读路径。开始只写新格式前,旧读程序须能理解新记录。 +库本身不会自动迁移应用记录。 + +对于一致性哈希或分片归属,规划迁移时保持所有路由器使用同一个显式环版本。 +从权威节点标识构建新环,比较分配结果,复制或重平衡受影响数据,再协调各参与方的 +路由 epoch 与归属切换。数据转移验收前,保留旧环和旧哈希入口。普通二进制滚动升级中 +混用新旧环,可能把同一键路由到不同位置;仅增加双查不会完成归属迁移。 + +`check-core` 纳入[上游 SMHasher 验证值](https://github.com/aappleby/smhasher/blob/master/src/main.cpp#L67-L70) +及其[前缀和种子验证过程](https://github.com/aappleby/smhasher/blob/master/src/KeysetTest.cpp#L11-L38), +并为旧 MSVC 策略保留独立的标量参考固定值。用例覆盖全部七个公开方法、MurmurHash3 的 +全部尾部长度、完整宽度种子、高位字节和旋转路径、MurmurHash2 块边界固定值,以及 +偏移 0 到 7 的精确分配字节缓冲区。AddressSanitizer 可检查分配末端越界读取, +UndefinedBehaviorSanitizer 可检查无效对齐。固定值验证哈希行为;应用数据迁移仍须独立验收。 + ## 验证 `check-typed-examples` 与 timers、module_lifecycle、entity_operations 一起加载真实 Utility DSO,验证四个接口的 RTTI 注册键与管理器归属、GUID 生成与解析、zlib/Zstandard 二进制往返、由所属线程时间推进触发的定时器,以及正常和重复停机。`check-app-presets` 使用仓库启动器预设;应用、示例、Kernel、Utility 开启时,`check-core` 包含这两组检查。独立 `tests/consumer` 项目的 `check-sdk` 在关闭运行时插件时编译公开接口与辅助头路径,并拒绝可见的实现或装配头。这些检查验证正确性,不代表性能或任意回调生命周期验收。 diff --git a/src/core/hash/murmur_hash.hpp b/src/core/hash/murmur_hash.hpp index 128e42bcf..20fbdc7fd 100644 --- a/src/core/hash/murmur_hash.hpp +++ b/src/core/hash/murmur_hash.hpp @@ -22,6 +22,12 @@ #pragma once +#include +#include +#include +#include +#include + #include "ark/macros.hpp" #include "ark/platform.hpp" @@ -31,6 +37,33 @@ class murmur_hash { public: static void murmur_hash3_x86_32(const void* key, int len, uint32_t seed, void* out) { + murmur_hash3_x86_32_impl(key, len, seed, out); + } + + void murmur_hash3_x86_128(const void* key, const int len, uint32_t seed, void* out) { + murmur_hash3_x86_128_impl(key, len, seed, out); + } + + static void murmur_hash3_x64_128(const void* key, const int len, const uint32_t seed, void* out) { + murmur_hash3_x64_128_impl(key, len, seed, out); + } + + // Migration-only access to hashes produced by the former MSVC block policy. + static void murmur_hash3_x86_32_legacy_msvc(const void* key, int len, uint32_t seed, void* out) { + murmur_hash3_x86_32_impl(key, len, seed, out); + } + + void murmur_hash3_x86_128_legacy_msvc(const void* key, const int len, uint32_t seed, void* out) { + murmur_hash3_x86_128_impl(key, len, seed, out); + } + + static void murmur_hash3_x64_128_legacy_msvc(const void* key, const int len, const uint32_t seed, void* out) { + murmur_hash3_x64_128_impl(key, len, seed, out); + } + +private: + template + static void murmur_hash3_x86_32_impl(const void* key, int len, uint32_t seed, void* out) { const uint8_t* data = (const uint8_t*)key; const int nblocks = len / 4; @@ -42,18 +75,18 @@ class murmur_hash //---------- // body - const uint32_t* blocks = (const uint32_t*)(data + nblocks * 4); + const uint8_t* blocks = data + nblocks * 4; for (int i = -nblocks; i; i++) { - uint32_t k1 = getblock(blocks, i); + uint32_t k1 = getblock(blocks, i); k1 *= c1; - k1 = rotl32(k1, 15); + k1 = std::rotl(k1, 15); k1 *= c2; h1 ^= k1; - h1 = rotl32(h1, 13); + h1 = std::rotl(h1, 13); h1 = h1 * 5 + 0xe6546b64; } @@ -73,7 +106,7 @@ class murmur_hash case 1: k1 ^= tail[0]; k1 *= c1; - k1 = rotl32(k1, 15); + k1 = std::rotl(k1, 15); k1 *= c2; h1 ^= k1; }; @@ -85,12 +118,13 @@ class murmur_hash h1 = fmix(h1); - *(uint32_t*)out = h1; + store_word(out, h1); } //----------------------------------------------------------------------------- - void murmur_hash3_x86_128(const void* key, const int len, uint32_t seed, void* out) { + template + static void murmur_hash3_x86_128_impl(const void* key, const int len, uint32_t seed, void* out) { const uint8_t* data = (const uint8_t*)key; const int nblocks = len / 16; @@ -107,48 +141,48 @@ class murmur_hash //---------- // body - const uint32_t* blocks = (const uint32_t*)(data + nblocks * 16); + const uint8_t* blocks = data + nblocks * 16; for (int i = -nblocks; i; i++) { - uint32_t k1 = getblock(blocks, i * 4 + 0); - uint32_t k2 = getblock(blocks, i * 4 + 1); - uint32_t k3 = getblock(blocks, i * 4 + 2); - uint32_t k4 = getblock(blocks, i * 4 + 3); + uint32_t k1 = getblock(blocks, i * 4 + 0); + uint32_t k2 = getblock(blocks, i * 4 + 1); + uint32_t k3 = getblock(blocks, i * 4 + 2); + uint32_t k4 = getblock(blocks, i * 4 + 3); k1 *= c1; - k1 = rotl32(k1, 15); + k1 = std::rotl(k1, 15); k1 *= c2; h1 ^= k1; - h1 = rotl32(h1, 19); + h1 = std::rotl(h1, 19); h1 += h2; h1 = h1 * 5 + 0x561ccd1b; k2 *= c2; - k2 = rotl32(k2, 16); + k2 = std::rotl(k2, 16); k2 *= c3; h2 ^= k2; - h2 = rotl32(h2, 17); + h2 = std::rotl(h2, 17); h2 += h3; h2 = h2 * 5 + 0x0bcaa747; k3 *= c3; - k3 = rotl32(k3, 17); + k3 = std::rotl(k3, 17); k3 *= c4; h3 ^= k3; - h3 = rotl32(h3, 15); + h3 = std::rotl(h3, 15); h3 += h4; h3 = h3 * 5 + 0x96cd1c35; k4 *= c4; - k4 = rotl32(k4, 18); + k4 = std::rotl(k4, 18); k4 *= c1; h4 ^= k4; - h4 = rotl32(h4, 13); + h4 = std::rotl(h4, 13); h4 += h1; h4 = h4 * 5 + 0x32ac3b17; } @@ -172,7 +206,7 @@ class murmur_hash case 13: k4 ^= tail[12] << 0; k4 *= c4; - k4 = rotl32(k4, 18); + k4 = std::rotl(k4, 18); k4 *= c1; h4 ^= k4; @@ -185,7 +219,7 @@ class murmur_hash case 9: k3 ^= tail[8] << 0; k3 *= c3; - k3 = rotl32(k3, 17); + k3 = std::rotl(k3, 17); k3 *= c4; h3 ^= k3; @@ -198,7 +232,7 @@ class murmur_hash case 5: k2 ^= tail[4] << 0; k2 *= c2; - k2 = rotl32(k2, 16); + k2 = std::rotl(k2, 16); k2 *= c3; h2 ^= k2; @@ -211,7 +245,7 @@ class murmur_hash case 1: k1 ^= tail[0] << 0; k1 *= c1; - k1 = rotl32(k1, 15); + k1 = std::rotl(k1, 15); k1 *= c2; h1 ^= k1; }; @@ -243,13 +277,12 @@ class murmur_hash h3 += h1; h4 += h1; - ((uint32_t*)out)[0] = h1; - ((uint32_t*)out)[1] = h2; - ((uint32_t*)out)[2] = h3; - ((uint32_t*)out)[3] = h4; + const uint32_t result[]{h1, h2, h3, h4}; + store_words(out, result); } - static void murmur_hash3_x64_128(const void* key, const int len, const uint32_t seed, void* out) { + template + static void murmur_hash3_x64_128_impl(const void* key, const int len, const uint32_t seed, void* out) { const uint8_t* data = (const uint8_t*)key; const int nblocks = len / 16; @@ -262,28 +295,28 @@ class murmur_hash //---------- // body - const uint64_t* blocks = (const uint64_t*)(data); + const uint8_t* blocks = data; for (int i = 0; i < nblocks; i++) { - uint64_t k1 = getblock(blocks, i * 2 + 0); - uint64_t k2 = getblock(blocks, i * 2 + 1); + uint64_t k1 = getblock(blocks, i * 2 + 0); + uint64_t k2 = getblock(blocks, i * 2 + 1); k1 *= c1; - k1 = rotl64(k1, 31); + k1 = std::rotl(k1, 31); k1 *= c2; h1 ^= k1; - h1 = rotl64(h1, 27); + h1 = std::rotl(h1, 27); h1 += h2; h1 = h1 * 5 + 0x52dce729; k2 *= c2; - k2 = rotl64(k2, 33); + k2 = std::rotl(k2, 33); k2 *= c1; h2 ^= k2; - h2 = rotl64(h2, 31); + h2 = std::rotl(h2, 31); h2 += h1; h2 = h2 * 5 + 0x38495ab5; } @@ -313,7 +346,7 @@ class murmur_hash case 9: k2 ^= uint64_t(tail[8]) << 0; k2 *= c2; - k2 = rotl64(k2, 33); + k2 = std::rotl(k2, 33); k2 *= c1; h2 ^= k2; @@ -334,7 +367,7 @@ class murmur_hash case 1: k1 ^= uint64_t(tail[0]) << 0; k1 *= c1; - k1 = rotl64(k1, 31); + k1 = std::rotl(k1, 31); k1 *= c2; h1 ^= k1; }; @@ -354,10 +387,11 @@ class murmur_hash h1 += h2; h2 += h1; - ((uint64_t*)out)[0] = h1; - ((uint64_t*)out)[1] = h2; + const uint64_t result[]{h1, h2}; + store_words(out, result); } +public: static void murmur_hash2(const void* key, int len, void* out) { uint32_t h, k; @@ -398,32 +432,52 @@ class murmur_hash h *= 0x5bd1e995; h ^= h >> 15; - *(uint32_t*)out = h; + store_word(out, h); } private: - inline static uint32_t rotl32(uint32_t x, int8_t r) { -#ifdef ARK_PLATFORM_WIN - return _rotl(x, r); -#else - return (x << r) | (x >> (32 - r)); -#endif + template + static word_type byte_swap(word_type value) { + static_assert(std::is_unsigned_v); + word_type result = 0; + for (std::size_t byte = 0; byte < sizeof(word_type); ++byte) + { + result = (result << 8) | (value & 0xff); + value >>= 8; + } + return result; } - inline static uint64_t rotl64(uint64_t x, int8_t r) { -#ifdef ARK_PLATFORM_WIN - return _rotl64(x, r); -#else - return (x << r) | (x >> (64 - r)); -#endif + template + static word_type getblock(const uint8_t* p, int i) { + // Retain byte-addressed arithmetic even for the backwards x86 block walk. + word_type value; + std::memcpy(&value, p + i * static_cast(sizeof(word_type)), sizeof(value)); + // Standard blocks are little endian; old MSVC blocks were byte-swapped + // on a little-endian host. Legacy tails and output remain little endian. + if constexpr ((legacy_msvc && std::endian::native == std::endian::little) || + (!legacy_msvc && std::endian::native == std::endian::big)) + { + value = byte_swap(value); + } + return value; } - static uint32_t getblock(const uint32_t* p, int i) { - return BYTESWAP32(p[i]); + template + static void store_word(void* out, word_type value) { + if constexpr (std::endian::native == std::endian::big) + { + value = byte_swap(value); + } + std::memcpy(out, &value, sizeof(value)); } - static uint64_t getblock(const uint64_t* p, int i) { - return BYTESWAP64(p[i]); + template + static void store_words(void* out, const word_type (&words)[count]) { + for (std::size_t index = 0; index < count; ++index) + { + store_word(static_cast(out) + index * sizeof(word_type), words[index]); + } } static uint32_t fmix(uint32_t h) { diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index e027b5fe2..4f68ab9e3 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -6,7 +6,7 @@ target_link_libraries(ark_core_header_checks PRIVATE ark::core) add_executable(ark_core_tests main.cpp strings.cpp containers.cpp templates.cpp callbacks.cpp header_regressions.cpp cxx20_language.cpp concurrency.cpp plugin_manager_startup.cpp data_strings.cpp node_data_storage.cpp - object_vectors.cpp plugin_construction.cpp module_factory.cpp random_generator.cpp xxtea.cpp) + object_vectors.cpp plugin_construction.cpp module_factory.cpp random_generator.cpp xxtea.cpp murmur_hash.cpp) target_link_libraries(ark_core_tests PRIVATE ark::core ark::kernel ark::utility) add_dependencies(ark_core_tests ark_core_header_checks) add_library(ark_construction_header_checks OBJECT headers/module_base.cpp headers/plugin_base.cpp) diff --git a/tests/core/README.md b/tests/core/README.md index 63864e4cd..d57d4a947 100644 --- a/tests/core/README.md +++ b/tests/core/README.md @@ -20,6 +20,16 @@ The public header checks include `` as the first include and include it twice. Compile-time checks preserve its unsigned 32-bit underlying type and every declared error value. + +Murmur regressions include independent upstream SMHasher verification constants, +legacy swapped-block fixtures, all tail lengths, full-width seeds and the original +rotation paths. They also check MurmurHash2 golden words, exact-size input +allocations and input/output offsets 0 through 7 with output guard bytes. +Run the core executable with `--test-case="*Murmur*"` to select these cases. +See [Murmur compatibility](../../docs/reference/utility.md#core-murmur-hashes) +for canonical little-endian output and the explicit legacy MSVC migration path. AddressSanitizer and +UndefinedBehaviorSanitizer builds exercise overread and alignment checks. + With `ARK_BUILD_PROTO=ON`, additional cases verify Protobuf parse failures, actor identity, binary messages, legacy wire payloads and external JSON field names. They also exercise the new `ark::protobuf` descriptor and enum names. diff --git a/tests/core/README_CN.md b/tests/core/README_CN.md index c33ba20d4..12571eea9 100644 --- a/tests/core/README_CN.md +++ b/tests/core/README_CN.md @@ -19,6 +19,14 @@ C++20 语言回归验证六种精确整数类型分类、限定符与引用处 随机数回归将公开头文件放在首个包含位置,并重复包含两次。测试保留固定的原始、有符号及无符号随机序列、最终种子和生成次数,并验证 `INT_MIN`/`INT_MAX` 极值、异号、相邻、逆序及相等端点的有符号半开区间。端点相同时直接返回该值,不消耗随机数。这些用例属于 `ark_core_tests` 和 `check-core`;使用 `-fsanitize=undefined -fno-sanitize-recover=all` 构建时,还会通过 UndefinedBehaviorSanitizer 检查范围运算。参见[随机数工具行为](../../docs/reference/utility_CN.md#core-随机数工具)。 + +Murmur 回归包含独立的上游 SMHasher 验证常量、旧版完整块交换策略固定值、全部尾部长度、 +完整宽度种子和原有旋转路径。用例还验证 MurmurHash2 固定值、精确分配的输入缓冲区、 +输入和输出偏移 0 到 7,以及输出保护字节。运行核心测试程序时,通过 +`--test-case="*Murmur*"` 选择这些用例。规范小端输出和显式旧 MSVC 迁移入口见 +[Murmur 兼容性说明](../../docs/reference/utility_CN.md#core-murmur-哈希)。 +AddressSanitizer 和 UndefinedBehaviorSanitizer 构建可检查越界读取和无效对齐。 + 公共头检查将 `` 放在首个包含位置,并重复包含两次;编译期断言 验证其无符号 32 位底层类型及所有已声明的错误值。 diff --git a/tests/core/murmur_hash.cpp b/tests/core/murmur_hash.cpp new file mode 100644 index 000000000..10dd516c6 --- /dev/null +++ b/tests/core/murmur_hash.cpp @@ -0,0 +1,251 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include + +// Keep the first inclusion separate to check standalone inclusion and the include guard. +#include +#include +#include +#include +#include +#include +#include + +#include "../network/doctest.h" + +#include + +namespace { + +static_assert(std::is_member_function_pointer_v); +static_assert(std::is_member_function_pointer_v); +static_assert(std::is_pointer_v); +static_assert(std::is_pointer_v); + +void hash_x86_128(const void* key, int length, std::uint32_t seed, void* output) { + ark::murmur_hash{}.murmur_hash3_x86_128(key, length, seed, output); +} + +void hash_x86_128_legacy(const void* key, int length, std::uint32_t seed, void* output) { + ark::murmur_hash{}.murmur_hash3_x86_128_legacy_msvc(key, length, seed, output); +} + +void hash_murmur2(const void* key, int length, std::uint32_t, void* output) { + ark::murmur_hash::murmur_hash2(key, length, output); +} + +template +std::uint32_t verification_value(hash_function hash) { + // Austin Appleby's SMHasher VerificationTest: all prefix lengths 0..255, + // seeds 256-length, then hash their concatenated digests with seed zero. + std::array key{}; + std::array hashes{}; + std::array result{}; + for (int length = 0; length < 256; ++length) + { + key[length] = static_cast(length); + hash(key.data(), length, 256 - length, hashes.data() + length * output_size); + } + hash(hashes.data(), static_cast(hashes.size()), 0, result.data()); + return std::uint32_t{result[0]} | (std::uint32_t{result[1]} << 8) | (std::uint32_t{result[2]} << 16) | + (std::uint32_t{result[3]} << 24); +} + +template +std::array little_words(const std::array& bytes) { + std::array words{}; + for (std::size_t word = 0; word < word_count; ++word) + { + for (std::size_t byte = 0; byte < sizeof(word_type); ++byte) + { + words[word] |= static_cast(bytes[word * sizeof(word_type) + byte]) << (byte * 8); + } + } + return words; +} + +template +void check_byte_buffers(hash_function hash) { + alignas(8) std::array key{}; + for (std::size_t index = 0; index < key.size(); ++index) + { + key[index] = static_cast(index * 37 + 0x80); + } + // Cover every tail path and both sides of several full-block boundaries. + for (int length = 0; length <= 257; ++length) + { + if (length > 65 && length != 127 && length != 128 && length != 129 && length != 255 && length != 256 && + length != 257) + { + continue; + } + for (const auto seed : {0u, 1u, 0x80000000u, 0xffffffffu}) + { + alignas(8) std::array expected{}; + hash(key.data(), length, seed, expected.data()); + for (std::size_t offset = 0; offset < 8; ++offset) + { + CAPTURE(length); + CAPTURE(seed); + CAPTURE(offset); + // The readable range ends at the allocation boundary, so a sanitizer + // can detect full-block reads past the exact input length. + auto input = std::make_unique(std::max(1, length + offset)); + std::copy_n(key.begin(), length, input.get() + offset); + alignas(8) std::array output; + output.fill(0xa5); + const auto output_offset = (offset + 3) % 8; + hash(input.get() + offset, length, seed, output.data() + output_offset); + CHECK(std::equal(expected.begin(), expected.end(), output.begin() + output_offset)); + CHECK(std::all_of( + output.begin(), output.begin() + output_offset, [](auto value) { return value == 0xa5; })); + CHECK(std::all_of(output.begin() + output_offset + output_size, output.end(), + [](auto value) { return value == 0xa5; })); + } + } + } +} + +} // namespace + +TEST_CASE("Murmur3 retains upstream verification values and the legacy swapped-block policy") { + // Independent standard values and verification procedure: + // https://github.com/aappleby/smhasher/blob/master/src/main.cpp#L67-L70 + // https://github.com/aappleby/smhasher/blob/master/src/KeysetTest.cpp#L11-L38 + // Swapped-block values were derived from the same public-domain reference + // with only full-block byte order reversed; they are ARK compatibility fixtures. + CHECK(verification_value<4>(ark::murmur_hash::murmur_hash3_x86_32) == 0xb0f57ee3u); + CHECK(verification_value<16>(hash_x86_128) == 0xb3ece62au); + CHECK(verification_value<16>(ark::murmur_hash::murmur_hash3_x64_128) == 0x6384ba69u); + CHECK(verification_value<4>(ark::murmur_hash::murmur_hash3_x86_32_legacy_msvc) == 0xd1ab67cfu); + CHECK(verification_value<16>(hash_x86_128_legacy) == 0x2df8b022u); + CHECK(verification_value<16>(ark::murmur_hash::murmur_hash3_x64_128_legacy_msvc) == 0x501e703bu); +} + +TEST_CASE("Murmur3 rotations preserve full-width seeds and multi-block golden words") { + std::array key{}; + for (std::size_t index = 0; index < key.size(); ++index) + { + key[index] = static_cast(index); + } + + struct golden_case + { + int length; + std::uint32_t seed; + std::uint32_t x86_32; + std::array x86_128; + std::array x64_128; + }; + + // Fixed scalar-reference vectors exercise every production rotation count, + // high-bit seed preservation, exact blocks and a multi-block tail. + const std::array little{{ + {16, 0xffffffffu, 0x9fd6e060u, {0x4f872353u, 0x3ffde8eeu, 0x0c593cefu, 0xd276cbb5u}, + {0x75298e938ac005bfull, 0x26873cd6ed1b3b1eull}}, + {33, 0x12345678u, 0x2a4f25b0u, {0x0101769du, 0x6e0928d4u, 0x5f9361deu, 0xea784760u}, + {0xa94d180f895f3670ull, 0x9635b8a3feb6f62full}}, + {33, 0xffffffffu, 0xa5737017u, {0x53948860u, 0xb573ee1du, 0xb2e1371cu, 0x7d39669eu}, + {0xf14b6deed9373a70ull, 0xf83f262a61312136ull}}, + }}; + const std::array swapped{{ + {16, 0xffffffffu, 0x2d93b016u, {0x84c0695cu, 0x73902949u, 0x6bfdce30u, 0xefc92f7cu}, + {0x703b8fedb7306652ull, 0xb1ce86b7edcd8628ull}}, + {33, 0x12345678u, 0xb255cc88u, {0xaea33583u, 0x1fd73070u, 0x36c553ffu, 0x9f396b6du}, + {0xa34f5dea69959035ull, 0x734c22e1993ef38full}}, + {33, 0xffffffffu, 0x74b4bba8u, {0xc8d61452u, 0x878dd688u, 0xad51e4b6u, 0x5b85edf7u}, + {0xcdcdff4c5a639071ull, 0x8332c8a91ba2cf3dull}}, + }}; + for (std::size_t index = 0; index < little.size(); ++index) + { + CAPTURE(index); + if (index == 2) + { + for (std::size_t byte = 0; byte < key.size(); ++byte) + { + key[byte] = static_cast(byte * 37 + 0x80); + } + } + for (const bool legacy : {false, true}) + { + CAPTURE(legacy); + const auto& expected = legacy ? swapped[index] : little[index]; + std::array actual32{}; + std::array actual86{}; + std::array actual64{}; + const auto hash32 = + legacy ? ark::murmur_hash::murmur_hash3_x86_32_legacy_msvc : ark::murmur_hash::murmur_hash3_x86_32; + const auto hash86 = legacy ? hash_x86_128_legacy : hash_x86_128; + const auto hash64 = + legacy ? ark::murmur_hash::murmur_hash3_x64_128_legacy_msvc : ark::murmur_hash::murmur_hash3_x64_128; + hash32(key.data(), expected.length, expected.seed, actual32.data()); + hash86(key.data(), expected.length, expected.seed, actual86.data()); + hash64(key.data(), expected.length, expected.seed, actual64.data()); + CHECK((little_words(actual32)[0] == expected.x86_32)); + CHECK((little_words(actual86) == expected.x86_128)); + CHECK((little_words(actual64) == expected.x64_128)); + } + } +} + +TEST_CASE("Murmur2 retains seed-zero golden words around block boundaries") { + std::array key{}; + for (std::size_t index = 0; index < key.size(); ++index) + { + key[index] = static_cast(index); + } + const std::array, 10> cases{ + {{0, 0}, {1, 0xe94e6ebdu}, {3, 0xe2558031u}, {4, 0xa4985988u}, {15, 0x8184d621u}, {16, 0x5f3c0743u}, + {17, 0xb1aad377u}, {31, 0x1d7d6ee1u}, {32, 0xf1bc6c23u}, {33, 0x915a6941u}}}; + for (const auto& [length, expected] : cases) + { + CAPTURE(length); + std::array result{}; + ark::murmur_hash::murmur_hash2(key.data(), length, result.data()); + CHECK((little_words(result)[0] == expected)); + } +} + +TEST_CASE("Murmur3 default and legacy policies agree until a complete block is present") { + const std::array key{ + 0x80, 0xff, 0x00, 0x01, 0xa5, 0x5a, 0x81, 0xfe, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09}; + using hash_function = void (*)(const void*, int, std::uint32_t, void*); + + struct policy_case + { + hash_function standard; + hash_function legacy; + int block_size; + }; + + const std::array policies{{ + {ark::murmur_hash::murmur_hash3_x86_32, ark::murmur_hash::murmur_hash3_x86_32_legacy_msvc, 4}, + {hash_x86_128, hash_x86_128_legacy, 16}, + {ark::murmur_hash::murmur_hash3_x64_128, ark::murmur_hash::murmur_hash3_x64_128_legacy_msvc, 16}, + }}; + for (const auto& policy : policies) + { + for (int length = 0; length < policy.block_size; ++length) + { + for (const auto seed : {0u, 0x80000000u, 0xffffffffu}) + { + CAPTURE(length); + CAPTURE(seed); + std::array standard{}; + std::array legacy{}; + policy.standard(key.data(), length, seed, standard.data()); + policy.legacy(key.data(), length, seed, legacy.data()); + CHECK(standard == legacy); + } + } + } +} + +TEST_CASE("All Murmur variants accept byte-aligned inputs and outputs without changing digest bytes") { + check_byte_buffers<4>(ark::murmur_hash::murmur_hash3_x86_32); + check_byte_buffers<16>(hash_x86_128); + check_byte_buffers<16>(ark::murmur_hash::murmur_hash3_x64_128); + check_byte_buffers<4>(hash_murmur2); + check_byte_buffers<4>(ark::murmur_hash::murmur_hash3_x86_32_legacy_msvc); + check_byte_buffers<16>(hash_x86_128_legacy); + check_byte_buffers<16>(ark::murmur_hash::murmur_hash3_x64_128_legacy_msvc); +} From d8b3a61529f5617656ac9dc1e760c297ffc2ac93 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 09:35:19 +0800 Subject: [PATCH 03/13] test(hash): isolate standalone Murmur header checks --- tests/core/CMakeLists.txt | 2 +- tests/core/headers/murmur_hash.cpp | 18 ++++++++++++++++++ tests/core/murmur_hash.cpp | 3 --- 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 tests/core/headers/murmur_hash.cpp diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 4f68ab9e3..a327d475d 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -1,7 +1,7 @@ add_library(ark_core_header_checks OBJECT headers/queue.cpp headers/guid128.cpp headers/rw_lock.cpp headers/array_map.cpp headers/bit_value.cpp headers/consistent_hash.cpp headers/list.cpp headers/lock_free_queue.cpp headers/lru_map.cpp headers/map.cpp headers/thread_safe_queue.cpp - headers/object_vector.cpp headers/error_code.cpp headers/logger.cpp) + headers/object_vector.cpp headers/error_code.cpp headers/logger.cpp headers/murmur_hash.cpp) target_link_libraries(ark_core_header_checks PRIVATE ark::core) add_executable(ark_core_tests main.cpp strings.cpp containers.cpp templates.cpp callbacks.cpp diff --git a/tests/core/headers/murmur_hash.cpp b/tests/core/headers/murmur_hash.cpp new file mode 100644 index 000000000..87c28955a --- /dev/null +++ b/tests/core/headers/murmur_hash.cpp @@ -0,0 +1,18 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include + +// Keep the second inclusion separate to retain the include-guard check. +#include + +void murmur_hash_header_check() { + const uint8_t key[16]{}; + uint8_t output[16]{}; + ark::murmur_hash hash; + hash.murmur_hash3_x86_32(key, 16, 0, output); + hash.murmur_hash3_x86_128(key, 16, 0, output); + hash.murmur_hash3_x64_128(key, 16, 0, output); + hash.murmur_hash3_x86_32_legacy_msvc(key, 16, 0, output); + hash.murmur_hash3_x86_128_legacy_msvc(key, 16, 0, output); + hash.murmur_hash3_x64_128_legacy_msvc(key, 16, 0, output); + hash.murmur_hash2(key, 16, output); +} diff --git a/tests/core/murmur_hash.cpp b/tests/core/murmur_hash.cpp index 10dd516c6..a0beb95c9 100644 --- a/tests/core/murmur_hash.cpp +++ b/tests/core/murmur_hash.cpp @@ -1,7 +1,4 @@ // Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. -#include - -// Keep the first inclusion separate to check standalone inclusion and the include guard. #include #include #include From b794cfcdfb3c8f565f143690e17bd6c06f2ce2b2 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 09:59:50 +0800 Subject: [PATCH 04/13] test(consul): exercise construction across application jobs --- .github/workflows/ci.yml | 1 + docs/reference/build-options.md | 2 ++ docs/reference/build-options_CN.md | 2 ++ tests/ci/package-dependencies.test.mjs | 10 ++++++++++ tests/consul/entry_factory.cpp | 2 +- 5 files changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36bb77872..de2ec5664 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -285,6 +285,7 @@ jobs: -DARK_BUILD_APPLICATIONS=ON -DARK_BUILD_BENCHMARKS=ON -DARK_BUILD_PLUGIN_HTTP=ON + -DARK_BUILD_PLUGIN_CONSUL=ON -DARK_BUILD_PROTO=ON -DARK_BUILD_TESTS=ON -DARK_BUILD_CORE_TESTS=ON diff --git a/docs/reference/build-options.md b/docs/reference/build-options.md index 52cd11ef9..5b2a5cb37 100644 --- a/docs/reference/build-options.md +++ b/docs/reference/build-options.md @@ -41,6 +41,8 @@ cmake --build build --target ark_mysql_plugin ark_redis_plugin All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction) and [Consul typed construction](../project-layout.md#typed-consul-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. +Applications CI enables the optional Consul plugin on Linux, macOS and Windows. Its `check-core` run includes construction, DLL lifecycle and production export checks; these checks require no Consul service and do not establish service interoperability. + Link a consumer to `ark::mysql` and/or `ark::redis` for the public headers and usage requirements. These public interface targets do not load a runtime library. Deploy the selected libraries with the launcher and add their stems to the application's manifest: ```toml diff --git a/docs/reference/build-options_CN.md b/docs/reference/build-options_CN.md index 447194a30..5782fb155 100644 --- a/docs/reference/build-options_CN.md +++ b/docs/reference/build-options_CN.md @@ -41,6 +41,8 @@ cmake --build build --target ark_mysql_plugin ark_redis_plugin 全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)与 [Consul 类型化构造](../project-layout_CN.md#consul-类型化构造)改变公开类布局,需要重建消费者。两者的私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 +应用 CI 在 Linux、macOS、Windows 启用可选 Consul 插件,`check-core` 包含构造、动态库生命周期与生产库导出检查。这些检查不需要 Consul 服务,也不代表服务互通验收。 + 业务插件链接 `ark::mysql` 和/或 `ark::redis`,以获取公开头文件和编译要求;公共接口目标不会加载运行时动态库。将所需动态库与启动器一起部署,并在应用清单中填写库名称: ```toml diff --git a/tests/ci/package-dependencies.test.mjs b/tests/ci/package-dependencies.test.mjs index c4d44d354..9fee41e8e 100644 --- a/tests/ci/package-dependencies.test.mjs +++ b/tests/ci/package-dependencies.test.mjs @@ -77,3 +77,13 @@ test('unsupported PhysX and simulate integrations fail explicitly instead of sea assert.doesNotMatch(source, /3rdparty|find_package\(PhysX/); } }); + +test('Applications CI enables Consul construction and production export checks without a service', () => { + const applications = read('.github/workflows/ci.yml').split(' applications:\n')[1].split('\n database:')[0]; + assert.match(applications, /-DARK_BUILD_PLUGIN_CONSUL=ON/); + assert.match(applications, /run: cmake --build ci-build --target check-core/); + const checks = read('tests/consul/CMakeLists.txt'); + assert.match(checks, /if\(TARGET ark_consul_plugin\)/); + assert.match(checks, /add_dependencies\(check-core check-consul-plugin\)/); + assert.match(checks, /add_dependencies\(check-core check-consul-construction\)/); +}); diff --git a/tests/consul/entry_factory.cpp b/tests/consul/entry_factory.cpp index d9f92b51b..5fbf51591 100644 --- a/tests/consul/entry_factory.cpp +++ b/tests/consul/entry_factory.cpp @@ -31,7 +31,7 @@ class entry_consul final : public ark::module_base return true; } - std::future> register_service(const ark::consul::service_data&) override { + std::future> register_service(const ::consul::service_data&) override { return {}; } From 87e24d42955f42b0d5452eecbdd4e5d34a066408 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 09:59:50 +0800 Subject: [PATCH 05/13] docs(hash): distinguish affected routing consumers --- docs/reference/utility.md | 2 ++ docs/reference/utility_CN.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/reference/utility.md b/docs/reference/utility.md index 22f3f5146..6be8ae7a6 100644 --- a/docs/reference/utility.md +++ b/docs/reference/utility.md @@ -107,6 +107,8 @@ little-endian bytes; `_legacy_msvc` specifically reproduces old MSVC behavior. ### Migrate stored keys and routing +The built-in `ark::consistent_hash` currently uses MurmurHash2, whose results remain unchanged on little-endian platforms. The routing guidance below applies to consumers that derive their ring or shard keys from the affected MurmurHash3 policy. + Identify each dataset's hash variant, seed, input encoding and previous compiler policy before upgrading. Keep that policy with the data or in an explicit schema version. A digest alone cannot reconstruct the original key. IDs, shard mappings, diff --git a/docs/reference/utility_CN.md b/docs/reference/utility_CN.md index f9262cc25..747cec544 100644 --- a/docs/reference/utility_CN.md +++ b/docs/reference/utility_CN.md @@ -93,6 +93,8 @@ MurmurHash2 保留固定种子零。 ### 迁移已存储键与路由 +框架内置 `ark::consistent_hash` 当前使用 MurmurHash2,在小端平台上的结果保持不变。以下路由迁移说明适用于使用受影响 MurmurHash3 策略构建环或分片键的调用方。 + 升级前,先确认每个数据集的哈希变体、种子、输入编码和旧编译器策略,并在数据中或 显式 schema 版本中记录该策略。单独的摘要无法还原原始键。由旧 MSVC 哈希派生的 ID、 分片映射、缓存键和持久化索引需要显式迁移,不能替换函数后直接重算路由。 From 15b09018e51e2caec7a90326920928b5ff57c7f6 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 10:12:32 +0800 Subject: [PATCH 06/13] docs(consul): publish construction guide in canonical pages --- docs/guides/plugins.md | 2 +- docs/guides/plugins_CN.md | 2 +- docs/project-layout.md | 14 -------------- docs/project-layout_CN.md | 14 -------------- docs/reference/build-options.md | 2 +- docs/reference/build-options_CN.md | 2 +- docs/reference/directory-structure.md | 14 ++++++++++++++ docs/reference/directory-structure_CN.md | 14 ++++++++++++++ src/plugins/README.md | 4 ++-- src/plugins/README_CN.md | 4 ++-- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/docs/guides/plugins.md b/docs/guides/plugins.md index 9c9e8f5cf..cd3fbcd5d 100644 --- a/docs/guides/plugins.md +++ b/docs/guides/plugins.md @@ -212,4 +212,4 @@ Net constructs its HTTP client through a private factory returning `std::unique_ Kernel also uses typed construction while retaining its public `*_impl` and `kernel_plugin` types and header paths. Its private factory header is not installed. Rebuild Kernel and consumers together because those public class layouts change; the [Kernel migration table](../reference/kernel.md#typed-kernel-construction) records unchanged interface keys, update selection and registration order. -Consul follows the same construction pattern while retaining all three public headers and types. Its private factory is not installed; the module keeps its interface RTTI key and disabled updates. Rebuild Consul and consumers together because its public implementation/plugin layouts change. See [Consul construction migration](../project-layout.md#typed-consul-construction) for test entry points and dependency conditions. +Consul follows the same construction pattern while retaining all three public headers and types. Its private factory is not installed; the module keeps its interface RTTI key and disabled updates. Rebuild Consul and consumers together because its public implementation/plugin layouts change. See [Consul construction migration](../reference/directory-structure.md#typed-consul-construction) for test entry points and dependency conditions. diff --git a/docs/guides/plugins_CN.md b/docs/guides/plugins_CN.md index 39ba62f2a..b66b700d4 100644 --- a/docs/guides/plugins_CN.md +++ b/docs/guides/plugins_CN.md @@ -199,4 +199,4 @@ Net 通过私有工厂返回 `std::unique_ptr`,并启用 Kernel 同样使用类型化构造,并保留公开 `*_impl`、`kernel_plugin` 类型与头路径;私有工厂头不安装。公开类布局发生变化,Kernel 与消费者需要一起重建。[Kernel 迁移表](../reference/kernel_CN.md#kernel-类型化构造)记录保持不变的接口键、update 选择与注册顺序。 -Consul 使用相同构造方式,保留全部三个公开头与类型。私有工厂不安装;模块保留接口 RTTI 键和禁用 update 的选择。公开实现/插件布局发生变化,Consul 与消费者必须一起重建。测试入口与依赖条件见 [Consul 构造迁移](../project-layout_CN.md#consul-类型化构造)。 +Consul 使用相同构造方式,保留全部三个公开头与类型。私有工厂不安装;模块保留接口 RTTI 键和禁用 update 的选择。公开实现/插件布局发生变化,Consul 与消费者必须一起重建。测试入口与依赖条件见 [Consul 构造迁移](../reference/directory-structure_CN.md#consul-类型化构造)。 diff --git a/docs/project-layout.md b/docs/project-layout.md index 79cff408b..6ce9b31b3 100644 --- a/docs/project-layout.md +++ b/docs/project-layout.md @@ -134,17 +134,3 @@ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel ``` Header mapping does not establish Consul, Kafka or OSS SDK runtime compatibility. Physic and `physics_scene` still require the unsupported legacy PhysX 3 integration and retain their explicit configuration failure; no passing PhysX build is claimed. - -## Typed Consul construction - -**Breaking ABI/layout change:** `consul_module_impl` now inherits `module_base` and `consul_plugin` inherits `plugin_base`. Rebuild Consul and consumers together. All three public headers, their `` paths, public types and request methods remain available through `ark::consul`; the interface itself is unchanged. The C++ plugin boundary does not promise compiler-independent ABI compatibility. - -The private `consul_factory.hpp` returns `std::unique_ptr` and is excluded from public mappings and installation. Explicit `DllEntryPlugin` and `DllExitPlugin` retain the existing platform exports and logger setup. Installation registers the single `consul_module` interface RTTI key with `module_update::disabled`; uninstallation removes and destroys that module. The implementation still inherits the default update method. Configuration storage, HTTP-client lookup, TOML loading, request serialization and endpoints are unchanged. - -With `ARK_BUILD_CORE_TESTS=ON`, `ARK_BUILD_PROTO=ON` and `ARK_BUILD_CONFIG=ON`, `check-consul-construction` runs the actual factory/assembly doctest cases and a controlled-factory DSO test of the actual entry/exit source. These cover public bases, factory metadata, interface registration, duplicate rejection, disabled updates, configuration handoff, ownership and repeated stop. They require Protobuf, toml++ and the normal core/arknet dependencies, but no runtime plugin or Consul service. For an existing build configured with those options: - -```sh -cmake --build build --target check-consul-construction --parallel 2 -``` - -`ARK_BUILD_PLUGIN_CONSUL=ON` also requires `ARK_BUILD_APPLICATIONS=ON` and remains off by default. It links the generated protocol target `ark::proto` directly. With the core tests enabled, `check-consul-plugin` additionally inspects the actual Consul DSO exports in a separate process; `check-core` includes the applicable Consul checks. The controlled factory does not exercise the production HTTP client or a Consul server. These construction checks do not establish live Consul interoperability, protocol acceptance or arbitrary hot-update safety. diff --git a/docs/project-layout_CN.md b/docs/project-layout_CN.md index a251e8d4e..82a8ee230 100644 --- a/docs/project-layout_CN.md +++ b/docs/project-layout_CN.md @@ -124,17 +124,3 @@ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel ``` 头文件映射检查不代表 Consul、Kafka、OSS SDK 的运行时兼容性已经验证。Physic 与 `physics_scene` 仍依赖未受支持的旧版 PhysX 3 集成,保留明确的配置失败;此处没有宣称 PhysX 构建通过。 - -## Consul 类型化构造 - -**ABI/布局不兼容变更:** `consul_module_impl` 现在继承 `module_base`,`consul_plugin` 继承 `plugin_base`。Consul 与消费者必须一起重建。原有三个公开头、`` 路径、公开类型与请求方法继续通过 `ark::consul` 提供,接口本身不变。C++ 插件边界不承诺跨编译器的 ABI 兼容性。 - -私有 `consul_factory.hpp` 返回 `std::unique_ptr`,不映射、不安装。显式 `DllEntryPlugin` 与 `DllExitPlugin` 保留原有平台导出和日志初始化。安装时按 `consul_module` 接口 RTTI 键注册唯一模块,显式使用 `module_update::disabled`;卸载时移除并销毁该模块。实现仍继承默认 update 方法。配置存储、HTTP 客户端查找、TOML 加载、请求序列化及端点保持不变。 - -开启 `ARK_BUILD_CORE_TESTS=ON`、`ARK_BUILD_PROTO=ON` 与 `ARK_BUILD_CONFIG=ON` 后,`check-consul-construction` 运行真实工厂/装配的 doctest 用例,以及使用受控工厂与真实入口/出口源码的 DSO 测试。检查覆盖公开基类、工厂元数据、接口注册、重复注册拒绝、禁用 update、配置传递、所有权与重复 stop。依赖 Protobuf、toml++ 及通常的 core/arknet 依赖,无需运行时插件或 Consul 服务。对于已按上述选项配置的构建: - -```sh -cmake --build build --target check-consul-construction --parallel 2 -``` - -`ARK_BUILD_PLUGIN_CONSUL=ON` 还要求 `ARK_BUILD_APPLICATIONS=ON`,默认保持关闭。插件直接链接生成协议目标 `ark::proto`。开启核心测试后,`check-consul-plugin` 还在独立进程中检查真实 Consul DSO 的导出符号;`check-core` 包含当前条件下可用的 Consul 检查。受控工厂不执行生产 HTTP 客户端或访问 Consul 服务。这些构造检查不代表真实 Consul 互通、协议验收或任意热更新安全。 diff --git a/docs/reference/build-options.md b/docs/reference/build-options.md index 5b2a5cb37..f6be18c32 100644 --- a/docs/reference/build-options.md +++ b/docs/reference/build-options.md @@ -39,7 +39,7 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction) and [Consul typed construction](../project-layout.md#typed-consul-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. +All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction) and [Consul typed construction](directory-structure.md#typed-consul-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. Applications CI enables the optional Consul plugin on Linux, macOS and Windows. Its `check-core` run includes construction, DLL lifecycle and production export checks; these checks require no Consul service and do not establish service interoperability. diff --git a/docs/reference/build-options_CN.md b/docs/reference/build-options_CN.md index 5782fb155..b57abfb08 100644 --- a/docs/reference/build-options_CN.md +++ b/docs/reference/build-options_CN.md @@ -39,7 +39,7 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)与 [Consul 类型化构造](../project-layout_CN.md#consul-类型化构造)改变公开类布局,需要重建消费者。两者的私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 +全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)与 [Consul 类型化构造](directory-structure_CN.md#consul-类型化构造)改变公开类布局,需要重建消费者。两者的私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 应用 CI 在 Linux、macOS、Windows 启用可选 Consul 插件,`check-core` 包含构造、动态库生命周期与生产库导出检查。这些检查不需要 Consul 服务,也不代表服务互通验收。 diff --git a/docs/reference/directory-structure.md b/docs/reference/directory-structure.md index bbb33359d..890acc45d 100644 --- a/docs/reference/directory-structure.md +++ b/docs/reference/directory-structure.md @@ -166,3 +166,17 @@ With `ARK_BUILD_APPLICATIONS=ON`, CMake builds seven server libraries: Game, Log ## Installed layout [Installation](../get-started/installation.md#install-and-consume-the-cmake-package) preserves logical `` headers under `/include/ark/`. CMake package files use the GNUInstallDirs library directory under `cmake/ark/`; selected launcher and runtime plugins install under `bin/`. Configuration samples and licensing are under `share/ark/`. Consumers use `find_package(ark CONFIG REQUIRED)` and `ark::core` / `ark::` targets; source consumers use `add_subdirectory` with the same target names. Neither requires a generated mapping path. Source/build directories are absent from installed exports; arknet and other dependencies remain external. `tests/install/` contains independent installed consumers and the disposable-producer relocation test. + +## Typed Consul construction + +**Breaking ABI/layout change:** `consul_module_impl` now inherits `module_base` and `consul_plugin` inherits `plugin_base`. Rebuild Consul and consumers together. All three public headers, their `` paths, public types and request methods remain available through `ark::consul`; the interface itself is unchanged. The C++ plugin boundary does not promise compiler-independent ABI compatibility. + +The private `consul_factory.hpp` returns `std::unique_ptr` and is excluded from public mappings and installation. Explicit `DllEntryPlugin` and `DllExitPlugin` retain the existing platform exports and logger setup. Installation registers the single `consul_module` interface RTTI key with `module_update::disabled`; uninstallation removes and destroys that module. The implementation still inherits the default update method. Configuration storage, HTTP-client lookup, TOML loading, request serialization and endpoints are unchanged. + +With `ARK_BUILD_CORE_TESTS=ON`, `ARK_BUILD_PROTO=ON` and `ARK_BUILD_CONFIG=ON`, `check-consul-construction` runs the actual factory/assembly doctest cases and a controlled-factory DSO test of the actual entry/exit source. These cover public bases, factory metadata, interface registration, duplicate rejection, disabled updates, configuration handoff, ownership and repeated stop. They require Protobuf, toml++ and the normal core/arknet dependencies, but no runtime plugin or Consul service. For an existing build configured with those options: + +```sh +cmake --build build --target check-consul-construction --parallel 2 +``` + +`ARK_BUILD_PLUGIN_CONSUL=ON` also requires `ARK_BUILD_APPLICATIONS=ON` and remains off by default. It links the generated protocol target `ark::proto` directly. With the core tests enabled, `check-consul-plugin` additionally inspects the actual Consul DSO exports in a separate process; `check-core` includes the applicable Consul checks. The controlled factory does not exercise the production HTTP client or a Consul server. These construction checks do not establish live Consul interoperability, protocol acceptance or arbitrary hot-update safety. diff --git a/docs/reference/directory-structure_CN.md b/docs/reference/directory-structure_CN.md index d83bc7044..18665fdf1 100644 --- a/docs/reference/directory-structure_CN.md +++ b/docs/reference/directory-structure_CN.md @@ -156,3 +156,17 @@ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel `login/interface/login_net_module.hpp`、`login/include/login_plugin.hpp` 等源码包含路径通过构建树中的 `src/server/compat-include/` 转发头保留,内部源码使用相邻头文件。转发清单保留原有全部 28 个头文件,包括实现与插件声明,但不包含新增的私有工厂头。该兼容映射独立于公开 `` 映射,不新增可安装的服务端 API。具体类的继承链变化后,C++ 消费方需要重新编译。 设置 `ARK_BUILD_APPLICATIONS=ON` 后,CMake 编译 Game、Login、Proxy、World、Master、Dir、Router 七个服务动态库。Simulate 源码采用相同创建方式,但仍不参与默认构建,并保留未受支持的 PhysX 3/MessagePack 集成配置失败。源码共置不代表该集成已经通过验证。定向创建检查及其边界见[服务器架构](../concepts/server-architecture_CN.md#验证服务端创建方式)。 + +## Consul 类型化构造 + +**ABI/布局不兼容变更:** `consul_module_impl` 现在继承 `module_base`,`consul_plugin` 继承 `plugin_base`。Consul 与消费者必须一起重建。原有三个公开头、`` 路径、公开类型与请求方法继续通过 `ark::consul` 提供,接口本身不变。C++ 插件边界不承诺跨编译器的 ABI 兼容性。 + +私有 `consul_factory.hpp` 返回 `std::unique_ptr`,不映射、不安装。显式 `DllEntryPlugin` 与 `DllExitPlugin` 保留原有平台导出和日志初始化。安装时按 `consul_module` 接口 RTTI 键注册唯一模块,显式使用 `module_update::disabled`;卸载时移除并销毁该模块。实现仍继承默认 update 方法。配置存储、HTTP 客户端查找、TOML 加载、请求序列化及端点保持不变。 + +开启 `ARK_BUILD_CORE_TESTS=ON`、`ARK_BUILD_PROTO=ON` 与 `ARK_BUILD_CONFIG=ON` 后,`check-consul-construction` 运行真实工厂/装配的 doctest 用例,以及使用受控工厂与真实入口/出口源码的 DSO 测试。检查覆盖公开基类、工厂元数据、接口注册、重复注册拒绝、禁用 update、配置传递、所有权与重复 stop。依赖 Protobuf、toml++ 及通常的 core/arknet 依赖,无需运行时插件或 Consul 服务。对于已按上述选项配置的构建: + +```sh +cmake --build build --target check-consul-construction --parallel 2 +``` + +`ARK_BUILD_PLUGIN_CONSUL=ON` 还要求 `ARK_BUILD_APPLICATIONS=ON`,默认保持关闭。插件直接链接生成协议目标 `ark::proto`。开启核心测试后,`check-consul-plugin` 还在独立进程中检查真实 Consul DSO 的导出符号;`check-core` 包含当前条件下可用的 Consul 检查。受控工厂不执行生产 HTTP 客户端或访问 Consul 服务。这些构造检查不代表真实 Consul 互通、协议验收或任意热更新安全。 diff --git a/src/plugins/README.md b/src/plugins/README.md index 66e429e98..bb41192b6 100755 --- a/src/plugins/README.md +++ b/src/plugins/README.md @@ -20,12 +20,12 @@ All official plugins co-locate their headers, implementation and assembly under Use `ARK_BUILD_PLUGIN_` to select a plugin. Optional integrations require their own dependencies and verification. [MySQL](../../docs/reference/mysql.md) provides queries, prepared statements and transactions; [Redis](../../docs/reference/redis.md) provides commands and ordered pipelines. Both use named configuration and manager-owned coroutine tasks. See [database plugin activation](../../docs/reference/build-options.md#native-database-plugins) for build and runtime selection. Lua and Actor integrations remain planned work. -See [project layout and public plugin interfaces](../../docs/project-layout.md) for targets and build commands. +See [project layout and public plugin interfaces](../../docs/reference/directory-structure.md) for targets and build commands. [Utility](../../docs/reference/utility.md) documents the four module interfaces, owner-driven timers and the implementation-header migration. Kernel co-locates its 38 existing headers with its cpp files in `src/plugins/kernel/`. `ark::kernel` preserves every `` path, including `data_impl.hpp`, `data_list_impl.hpp` and `kernel_plugin.hpp`. The retained public implementation/plugin types use typed construction with private owning factories and explicit entry points. `kernel_factory.hpp` is neither mapped nor installed. The public class layouts change, so rebuild Kernel and consumers together; registration keys, update selection and lifecycle order are preserved. See [Kernel construction migration](../../docs/reference/kernel.md#typed-kernel-construction) for compatibility boundaries, consumption checks and Protobuf requirements. -Consul retains its three public headers and implementation/plugin types while using typed construction, a private owning factory and explicit entry points. Rebuild Consul and consumers together because the public class layouts change. The single interface registration key, disabled updates and request behavior are preserved. See [Consul construction migration](../../docs/project-layout.md#typed-consul-construction) for build conditions and service-free construction checks. +Consul retains its three public headers and implementation/plugin types while using typed construction, a private owning factory and explicit entry points. Rebuild Consul and consumers together because the public class layouts change. The single interface registration key, disabled updates and request behavior are preserved. See [Consul construction migration](../../docs/reference/directory-structure.md#typed-consul-construction) for build conditions and service-free construction checks. The other four legacy plugin families retain 19 byte-identical headers. Mapping checks do not validate Consul, Kafka or OSS SDK runtime integrations. Physic retains the unsupported PhysX 3 configuration failure; see [optional legacy dependencies](../../docs/dependencies.md#optional-legacy-integrations). diff --git a/src/plugins/README_CN.md b/src/plugins/README_CN.md index 82c42abd5..bed2c6eb9 100644 --- a/src/plugins/README_CN.md +++ b/src/plugins/README_CN.md @@ -20,12 +20,12 @@ 使用 `ARK_BUILD_PLUGIN_<名称大写>` 选择插件。可选插件需要各自的依赖和验证。[MySQL](../../docs/reference/mysql_CN.md) 提供查询、预处理语句和事务;[Redis](../../docs/reference/redis_CN.md) 提供命令和有序流水线。两者均通过具名配置和管理器托管的协程任务使用。构建与运行时选择见[数据库插件启用方式](../../docs/reference/build-options_CN.md#原生数据库插件)。Lua、Actor 的整合仍在规划中。 -CMake target 和构建命令见[项目布局与公共插件接口](../../docs/project-layout_CN.md)。 +CMake target 和构建命令见[项目布局与公共插件接口](../../docs/reference/directory-structure_CN.md)。 [Utility 参考](../../docs/reference/utility_CN.md)说明四个模块接口、所属线程驱动的定时器与实现头迁移。 Kernel 的原有 38 个头与 cpp 共置于 `src/plugins/kernel/`,通过 `ark::kernel` 保留全部 `` 路径,包括 `data_impl.hpp`、`data_list_impl.hpp` 和 `kernel_plugin.hpp`。保留的公开实现类型与插件类型使用类型化构造、私有拥有型工厂和显式入口。`kernel_factory.hpp` 不映射、不安装。公开类布局发生变化,Kernel 与消费者必须一起重建;注册键、update 选择和生命周期顺序保持不变。兼容边界、消费检查及 Protobuf 条件见 [Kernel 构造迁移](../../docs/reference/kernel_CN.md#kernel-类型化构造)。 -Consul 保留原有三个公开头及实现/插件类型,同时使用类型化构造、私有拥有型工厂和显式入口。公开类布局发生变化,Consul 与消费者必须一起重建。唯一接口注册键、禁用 update 与请求行为保持不变。构建条件及无需服务的构造检查见 [Consul 构造迁移](../../docs/project-layout_CN.md#consul-类型化构造)。 +Consul 保留原有三个公开头及实现/插件类型,同时使用类型化构造、私有拥有型工厂和显式入口。公开类布局发生变化,Consul 与消费者必须一起重建。唯一接口注册键、禁用 update 与请求行为保持不变。构建条件及无需服务的构造检查见 [Consul 构造迁移](../../docs/reference/directory-structure_CN.md#consul-类型化构造)。 其他四类旧插件保留原有 19 个头的全部字节。映射检查不代表 Consul、Kafka、OSS SDK 的运行时集成已经验证。Physic 保留未受支持的 PhysX 3 配置失败;参见[可选旧集成依赖](../../docs/dependencies_CN.md)。 From 7f2256fa31fedfb3494954419979bd8a71a658b4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Oct 2026 02:38:53 +0000 Subject: [PATCH 07/13] build(deps): bump fast-uri from 3.1.8 to 3.1.9 in /docs/site Bumps [fast-uri](https://github.com/fastify/fast-uri) from 3.1.8 to 3.1.9. - [Release notes](https://github.com/fastify/fast-uri/releases) - [Commits](https://github.com/fastify/fast-uri/compare/v3.1.8...v3.1.9) --- updated-dependencies: - dependency-name: fast-uri dependency-version: 3.1.9 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- docs/site/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/site/package-lock.json b/docs/site/package-lock.json index 37ca23691..cf2c3678b 100644 --- a/docs/site/package-lock.json +++ b/docs/site/package-lock.json @@ -3442,9 +3442,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", - "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "dev": true, "funding": [ { From 3bed67089ac2e8a2cf38f43e55dcf4f4f3c3e9b3 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 10:52:38 +0800 Subject: [PATCH 08/13] refactor(proto): remove unused codec scaffolding --- docs/reference/directory-structure.md | 2 +- docs/reference/directory-structure_CN.md | 2 +- docs/reference/protobuf.md | 8 ++- docs/reference/protobuf_CN.md | 8 ++- src/proto/codec/codec.hpp | 68 ------------------------ src/proto/codec/protobuf_codec.cpp | 43 --------------- src/proto/codec/protobuf_codec.hpp | 40 -------------- 7 files changed, 16 insertions(+), 155 deletions(-) delete mode 100644 src/proto/codec/codec.hpp delete mode 100644 src/proto/codec/protobuf_codec.cpp delete mode 100644 src/proto/codec/protobuf_codec.hpp diff --git a/docs/reference/directory-structure.md b/docs/reference/directory-structure.md index bbb33359d..146c5f087 100644 --- a/docs/reference/directory-structure.md +++ b/docs/reference/directory-structure.md @@ -13,7 +13,7 @@ Build artifacts belong to each CMake build directory: `cmake -S . -B out/release | `src/core/` | Core headers, implementation and CMake usage requirements | | `src/application/` | Application entry point and shutdown request | | `src/config/` | Application manifest declarations, shared compiled file/TOML reader and private diagnostics | -| `src/proto/` | Protocol schemas and codec implementation | +| `src/proto/` | Protocol schemas and CMake rules for generated Protobuf messages | | `src/server/` | Built-in server applications | | `src/plugins//` | All official plugin headers, implementation and assembly | | `tests/` | Core, network and private database regressions | diff --git a/docs/reference/directory-structure_CN.md b/docs/reference/directory-structure_CN.md index d83bc7044..95098e10c 100644 --- a/docs/reference/directory-structure_CN.md +++ b/docs/reference/directory-structure_CN.md @@ -12,7 +12,7 @@ ark 使用 C++20。核心与官方插件放在本仓库,业务插件放在应 | `src/core/` | 核心头文件、实现与 CMake 使用要求 | | `src/application/` | 应用入口与停服请求 | | `src/config/` | 应用清单声明、共享的文件/TOML 读取实现与私有诊断 | -| `src/proto/` | 协议定义与编解码实现 | +| `src/proto/` | 协议定义与 Protobuf 消息生成的 CMake 规则 | | `src/server/` | 内置服务端应用 | | `src/plugins//` | 全部官方插件头文件、实现与装配 | | `tests/` | 核心、网络与私有数据库回归测试 | diff --git a/docs/reference/protobuf.md b/docs/reference/protobuf.md index 362dd0b45..2b9087650 100644 --- a/docs/reference/protobuf.md +++ b/docs/reference/protobuf.md @@ -1,6 +1,6 @@ # Protobuf messages and compatibility -Protocol definitions live in `src/proto/proto/`; codec implementation lives in `src/proto/codec/`. Source files, messages, fields and enum identifiers use `snake_case`. The former `AFMsg` package is now `ark.protobuf`, exposed in C++ as `ark::protobuf`. The `consul` and `osslog` packages retain their names. +Protocol definitions live in `src/proto/proto/`. Source files, messages, fields and enum identifiers use `snake_case`. The former `AFMsg` package is now `ark.protobuf`, exposed in C++ as `ark::protobuf`. The `consul` and `osslog` packages retain their names. ## Build and include @@ -16,6 +16,12 @@ Generated C++ files remain in the CMake build directory under `src/proto/generat Use Protobuf, Abseil and utf8_range from one compatible installation prefix. In a manual package selection, align `Protobuf_DIR`, `absl_DIR` and `utf8_range_DIR`; a successful link alone does not validate reflection/JSON behavior. +## Message serialization + +Messages use the generated Protobuf API directly. The Bus module's message send paths in `src/plugins/bus/msg_module_impl.cpp` call `SerializeToString` and return `false` if serialization fails. The public `msg_module::decode_protobuf` helper in `` calls `ParseFromString` on the network message body, returning `false` on a parse failure and copying the actor ID on success. See [Bus](bus.md) for the message and transport boundary. + +ark does not provide a pluggable message codec API. The generated messages and these Bus helpers are the existing serialization path. + ## Migration contract | Preserved | Requires consumer updates | diff --git a/docs/reference/protobuf_CN.md b/docs/reference/protobuf_CN.md index bdf233809..c17e15b07 100644 --- a/docs/reference/protobuf_CN.md +++ b/docs/reference/protobuf_CN.md @@ -1,6 +1,6 @@ # Protobuf 消息与兼容边界 -协议定义位于 `src/proto/proto/`,编解码实现位于 `src/proto/codec/`。源码文件、消息、字段与枚举标识符采用 `snake_case`。原 `AFMsg` package 改为 `ark.protobuf`,C++ 命名空间为 `ark::protobuf`;`consul`、`osslog` 保留既有 package。 +协议定义位于 `src/proto/proto/`。源码文件、消息、字段与枚举标识符采用 `snake_case`。原 `AFMsg` package 改为 `ark.protobuf`,C++ 命名空间为 `ark::protobuf`;`consul`、`osslog` 保留既有 package。 ## 构建与包含 @@ -16,6 +16,12 @@ target_link_libraries(my_service PRIVATE ark::core ark::proto) Protobuf、Abseil 与 utf8_range 应来自同一套兼容安装。手工选择软件包时,同步检查 `Protobuf_DIR`、`absl_DIR` 与 `utf8_range_DIR`;链接成功本身不能证明反射和 JSON 行为正确。 +## 消息序列化 + +消息直接使用 Protobuf 生成的 API。Bus 模块在 `src/plugins/bus/msg_module_impl.cpp` 的消息发送流程中调用 `SerializeToString`,序列化失败时返回 `false`。`` 中的公开辅助接口 `msg_module::decode_protobuf` 对网络消息体调用 `ParseFromString`,解析失败时返回 `false`,成功时复制 actor ID。消息与传输的边界见 [Bus](bus_CN.md)。 + +ark 不提供可替换的消息 codec API。现有序列化流程使用生成的消息与上述 Bus 辅助接口。 + ## 命名迁移约定 | 保持不变 | 调用方需要调整 | diff --git a/src/proto/codec/codec.hpp b/src/proto/codec/codec.hpp deleted file mode 100644 index 1df371ec1..000000000 --- a/src/proto/codec/codec.hpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * This source file is part of ArkNX - * For the latest info, see https://github.com/ArkNX - * - * Copyright (c) 2013-2020 ArkNX authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#pragma once - -#include "ark/platform.hpp" - -namespace ark { - -/** - * message codec types - */ -enum class codec_type : uint8_t -{ - unknown = 0, /// unknown codec - protobuf = 1, /// protobuffer codes - json = 2, /// json codec - custom = 3, /// custom codec - websocket_protobuf = 4, /// websocket extension protobuffer data codec - websocket_json = 5, /// websocket extension json data codec -}; - -/** - * codec status - */ - -enum class codec_status : uint8_t -{ - ok = 1, /// OK - invalid = 2, /// different codec type - error = 3, /// codec error -}; - -class codec -{ -public: - // AFICodec(ARK_CODEC_TYPE type) - // { - // set_codec_type(type); - // } - - virtual ~codec() = default; - - virtual codec_status encode() = 0; - virtual codec_status decode() = 0; - - virtual codec_type get_codec_type() const = 0; - virtual void set_codec_type(codec_type type) = 0; -}; - -} // namespace ark diff --git a/src/proto/codec/protobuf_codec.cpp b/src/proto/codec/protobuf_codec.cpp deleted file mode 100644 index d2bf79dfc..000000000 --- a/src/proto/codec/protobuf_codec.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * This source file is part of ArkNX - * For the latest info, see https://github.com/ArkNX - * - * Copyright (c) 2013-2020 ArkNX authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#include "protobuf_codec.hpp" - -namespace ark { - -ark::codec_status protobuf_codec::encode() { - // TODO: - return codec_status::ok; -} - -ark::codec_status protobuf_codec::decode() { - // TODO: - return codec_status::ok; -} - -codec_type protobuf_codec::get_codec_type() const { - return codec_type::protobuf; -} - -void protobuf_codec::set_codec_type(codec_type type) { - type_ = codec_type::protobuf; -} - -} // namespace ark diff --git a/src/proto/codec/protobuf_codec.hpp b/src/proto/codec/protobuf_codec.hpp deleted file mode 100644 index 5b04c118c..000000000 --- a/src/proto/codec/protobuf_codec.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * This source file is part of ArkNX - * For the latest info, see https://github.com/ArkNX - * - * Copyright (c) 2013-2020 ArkNX authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ - -#pragma once - -#include "codec.hpp" - -namespace ark { - -class protobuf_codec : public codec -{ -public: - codec_status encode() override; - codec_status decode() override; - - codec_type get_codec_type() const override; - void set_codec_type(codec_type type) override; - -private: - codec_type type_{codec_type::unknown}; -}; - -} // namespace ark From 53222f85ba62f57997a5c58c671c9bc8567db9f0 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 10:53:11 +0800 Subject: [PATCH 09/13] refactor(hash): remove platform header dependencies --- docs/reference/utility.md | 9 +++++++++ docs/reference/utility_CN.md | 6 ++++++ src/core/container/consistent_hash.hpp | 1 + src/core/hash/murmur_hash.hpp | 11 ++++------- tests/core/CMakeLists.txt | 4 +++- tests/core/README.md | 13 +++++++++++++ tests/core/README_CN.md | 11 +++++++++++ tests/core/hash_header/CMakeLists.txt | 16 ++++++++++++++++ 8 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 tests/core/hash_header/CMakeLists.txt diff --git a/docs/reference/utility.md b/docs/reference/utility.md index 6be8ae7a6..1cadd53e8 100644 --- a/docs/reference/utility.md +++ b/docs/reference/utility.md @@ -76,6 +76,15 @@ tail mixing, result word order and the existing public signatures are unchanged; `murmur_hash3_x86_128` remains an instance method, and the other variants remain static. MurmurHash2 retains its fixed seed zero. +The Murmur header itself uses only standard C++20 headers. It does not include +`ark/macros.hpp` or `ark/platform.hpp`; callers using their declarations or macros +must include those headers explicitly. Public macro definitions are unchanged. +This header independence does not change `ark::core` or its CMake package's +dependency requirements. `check-murmur-hash-header` compiles the logical public +header with an isolated include directory and no SDK target dependencies; it is +also included in `check-core`. See [core regressions](../../tests/core/README.md) +for the standalone check commands. + ### Digest compatibility and legacy access **Breaking change:** default MurmurHash3 now reads full blocks as little endian diff --git a/docs/reference/utility_CN.md b/docs/reference/utility_CN.md index 747cec544..18fb89616 100644 --- a/docs/reference/utility_CN.md +++ b/docs/reference/utility_CN.md @@ -68,6 +68,12 @@ MurmurHash3 使用 C++20 `std::rotl`,旋转次数保持不变。种子、尾 原有公开签名不变;`murmur_hash3_x86_128` 仍为实例方法,其余变体仍为静态方法。 MurmurHash2 保留固定种子零。 +Murmur 头文件自身仅使用标准 C++20 头文件,不再包含 `ark/macros.hpp` 或 +`ark/platform.hpp`。调用方若使用其中的声明或宏,须显式包含对应头文件;公开宏定义 +保持不变。头文件独立性不改变 `ark::core` 或其 CMake 包的依赖要求。 +`check-murmur-hash-header` 仅通过隔离的包含目录编译逻辑公开头文件,不依赖 SDK +目标,并纳入 `check-core`。独立检查命令见[核心回归](../../tests/core/README_CN.md)。 + ### 摘要兼容性与旧结果入口 **不兼容变更:** 默认 MurmurHash3 现在在所有支持的平台上按小端方式读取完整块, diff --git a/src/core/container/consistent_hash.hpp b/src/core/container/consistent_hash.hpp index 2932a634e..3c8f8fb6f 100644 --- a/src/core/container/consistent_hash.hpp +++ b/src/core/container/consistent_hash.hpp @@ -23,6 +23,7 @@ #include "ark/dep.hpp" #include "ark/hash/crc.hpp" #include "ark/hash/murmur_hash.hpp" +#include "ark/macros.hpp" namespace ark { diff --git a/src/core/hash/murmur_hash.hpp b/src/core/hash/murmur_hash.hpp index 20fbdc7fd..a7b793c17 100644 --- a/src/core/hash/murmur_hash.hpp +++ b/src/core/hash/murmur_hash.hpp @@ -28,9 +28,6 @@ #include #include -#include "ark/macros.hpp" -#include "ark/platform.hpp" - namespace ark { class murmur_hash @@ -289,8 +286,8 @@ class murmur_hash uint64_t h1 = seed; uint64_t h2 = seed; - const uint64_t c1 = BIG_CONSTANT(0x87c37b91114253d5); - const uint64_t c2 = BIG_CONSTANT(0x4cf5ad432745937f); + const uint64_t c1 = 0x87c37b91114253d5ULL; + const uint64_t c2 = 0x4cf5ad432745937fULL; //---------- // body @@ -492,9 +489,9 @@ class murmur_hash static uint64_t fmix(uint64_t k) { k ^= k >> 33; - k *= BIG_CONSTANT(0xff51afd7ed558ccd); + k *= 0xff51afd7ed558ccdULL; k ^= k >> 33; - k *= BIG_CONSTANT(0xc4ceb9fe1a85ec53); + k *= 0xc4ceb9fe1a85ec53ULL; k ^= k >> 33; return k; diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index a327d475d..d1d541cce 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -4,11 +4,13 @@ add_library(ark_core_header_checks OBJECT headers/queue.cpp headers/guid128.cpp headers/object_vector.cpp headers/error_code.cpp headers/logger.cpp headers/murmur_hash.cpp) target_link_libraries(ark_core_header_checks PRIVATE ark::core) +add_subdirectory(hash_header) + add_executable(ark_core_tests main.cpp strings.cpp containers.cpp templates.cpp callbacks.cpp header_regressions.cpp cxx20_language.cpp concurrency.cpp plugin_manager_startup.cpp data_strings.cpp node_data_storage.cpp object_vectors.cpp plugin_construction.cpp module_factory.cpp random_generator.cpp xxtea.cpp murmur_hash.cpp) target_link_libraries(ark_core_tests PRIVATE ark::core ark::kernel ark::utility) -add_dependencies(ark_core_tests ark_core_header_checks) +add_dependencies(ark_core_tests ark_core_header_checks ark_murmur_hash_standalone_check) add_library(ark_construction_header_checks OBJECT headers/module_base.cpp headers/plugin_base.cpp) set_property(TARGET ark_construction_header_checks PROPERTY LINK_LIBRARIES ark::core) add_dependencies(ark_core_tests ark_construction_header_checks) diff --git a/tests/core/README.md b/tests/core/README.md index d57d4a947..bfe66a033 100644 --- a/tests/core/README.md +++ b/tests/core/README.md @@ -30,6 +30,19 @@ See [Murmur compatibility](../../docs/reference/utility.md#core-murmur-hashes) for canonical little-endian output and the explicit legacy MSVC migration path. AddressSanitizer and UndefinedBehaviorSanitizer builds exercise overread and alignment checks. +The existing mapped public-header check remains. An additional compile-only check +stages just `` and uses C++20 without project, platform, +spdlog or Boost include paths or SDK target dependencies. It is part of +`check-core` and can also run without configuring the full dependency graph: + +```sh +cmake -S tests/core/hash_header -B hash-header-build -G Ninja +cmake --build hash-header-build --target check-murmur-hash-header +``` + +Header independence does not remove the dependencies required by `ark::core` or +its CMake package. + With `ARK_BUILD_PROTO=ON`, additional cases verify Protobuf parse failures, actor identity, binary messages, legacy wire payloads and external JSON field names. They also exercise the new `ark::protobuf` descriptor and enum names. diff --git a/tests/core/README_CN.md b/tests/core/README_CN.md index 12571eea9..56005e362 100644 --- a/tests/core/README_CN.md +++ b/tests/core/README_CN.md @@ -27,6 +27,17 @@ Murmur 回归包含独立的上游 SMHasher 验证常量、旧版完整块交换 [Murmur 兼容性说明](../../docs/reference/utility_CN.md#core-murmur-哈希)。 AddressSanitizer 和 UndefinedBehaviorSanitizer 构建可检查越界读取和无效对齐。 +现有公开头映射检查继续保留。新增的纯编译检查仅映射 ``, +使用 C++20,不引入项目、平台、spdlog 或 Boost 包含路径,也不依赖 SDK 目标。 +该检查纳入 `check-core`,也可独立运行,无需配置完整依赖图: + +```sh +cmake -S tests/core/hash_header -B hash-header-build -G Ninja +cmake --build hash-header-build --target check-murmur-hash-header +``` + +头文件独立性不改变 `ark::core` 或其 CMake 包所需的依赖。 + 公共头检查将 `` 放在首个包含位置,并重复包含两次;编译期断言 验证其无符号 32 位底层类型及所有已声明的错误值。 diff --git a/tests/core/hash_header/CMakeLists.txt b/tests/core/hash_header/CMakeLists.txt new file mode 100644 index 000000000..f12fbb21b --- /dev/null +++ b/tests/core/hash_header/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.21) +project(ark_murmur_hash_header_check LANGUAGES CXX) + +# Only this logical public header is available; no project or third-party SDK. +set(hash_include_dir "${CMAKE_CURRENT_BINARY_DIR}/include") +configure_file("${CMAKE_CURRENT_LIST_DIR}/../../../src/core/hash/murmur_hash.hpp" + "${hash_include_dir}/ark/hash/murmur_hash.hpp" COPYONLY) +add_library(ark_murmur_hash_standalone_check OBJECT ../headers/murmur_hash.cpp) +# Drop the parent project's directory-wide ark::ark link and include paths. +set_target_properties(ark_murmur_hash_standalone_check PROPERTIES + LINK_LIBRARIES "" + INCLUDE_DIRECTORIES "${hash_include_dir}" + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF) +add_custom_target(check-murmur-hash-header DEPENDS ark_murmur_hash_standalone_check) From 2f6cdf404c2f62f5e184ffbe791e08b3432e8b70 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 12:11:24 +0800 Subject: [PATCH 10/13] refactor(etcd)!: replace plugin creation macros Use typed module and plugin bases with a private interface-returning factory and explicit entry points. Retain every public header, default plugin selection and existing Etcd request implementation. Add factory, ownership and production DSO checks to Applications acceptance. BREAKING CHANGE: etcd_client_module_impl and etcd_client_plugin have changed public class layouts. Rebuild Etcd Client and all consumers together; existing interfaces and logical header paths remain available. --- .github/workflows/ci.yml | 1 + docs/guides/plugins.md | 2 + docs/guides/plugins_CN.md | 2 + docs/reference/build-options.md | 4 +- docs/reference/build-options_CN.md | 4 +- docs/reference/directory-structure.md | 16 +++++ docs/reference/directory-structure_CN.md | 16 +++++ .../etcd_client/etcd_client_factory.hpp | 13 ++++ .../etcd_client/etcd_client_module_impl.cpp | 5 ++ .../etcd_client/etcd_client_module_impl.hpp | 10 ++-- .../etcd_client/etcd_client_plugin.cpp | 23 ++++++-- .../etcd_client/etcd_client_plugin.hpp | 12 +++- tests/ci/install-package.test.mjs | 4 +- tests/ci/package-dependencies.test.mjs | 17 ++++++ tests/consumer/etcd_client/main.cpp | 8 ++- tests/core/CMakeLists.txt | 5 ++ tests/etcd_client/CMakeLists.txt | 35 +++++++++++ tests/etcd_client/construction.cpp | 56 ++++++++++++++++++ tests/etcd_client/entry.cpp | 45 ++++++++++++++ tests/etcd_client/entry_factory.cpp | 59 +++++++++++++++++++ tests/etcd_client/exports.cpp | 31 ++++++++++ 21 files changed, 347 insertions(+), 21 deletions(-) create mode 100644 src/plugins/etcd_client/etcd_client_factory.hpp create mode 100644 tests/etcd_client/CMakeLists.txt create mode 100644 tests/etcd_client/construction.cpp create mode 100644 tests/etcd_client/entry.cpp create mode 100644 tests/etcd_client/entry_factory.cpp create mode 100644 tests/etcd_client/exports.cpp diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de2ec5664..4730be9fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -286,6 +286,7 @@ jobs: -DARK_BUILD_BENCHMARKS=ON -DARK_BUILD_PLUGIN_HTTP=ON -DARK_BUILD_PLUGIN_CONSUL=ON + -DARK_BUILD_PLUGIN_ETCD_CLIENT=ON -DARK_BUILD_PROTO=ON -DARK_BUILD_TESTS=ON -DARK_BUILD_CORE_TESTS=ON diff --git a/docs/guides/plugins.md b/docs/guides/plugins.md index cd3fbcd5d..7afc46cf7 100644 --- a/docs/guides/plugins.md +++ b/docs/guides/plugins.md @@ -213,3 +213,5 @@ Net constructs its HTTP client through a private factory returning `std::unique_ Kernel also uses typed construction while retaining its public `*_impl` and `kernel_plugin` types and header paths. Its private factory header is not installed. Rebuild Kernel and consumers together because those public class layouts change; the [Kernel migration table](../reference/kernel.md#typed-kernel-construction) records unchanged interface keys, update selection and registration order. Consul follows the same construction pattern while retaining all three public headers and types. Its private factory is not installed; the module keeps its interface RTTI key and disabled updates. Rebuild Consul and consumers together because its public implementation/plugin layouts change. See [Consul construction migration](../reference/directory-structure.md#typed-consul-construction) for test entry points and dependency conditions. + +Etcd Client also retains all five public headers and types while using typed bases, a private interface-returning factory and explicit entry/exit functions. Its interface RTTI key and disabled updates are unchanged. Rebuild the plugin and consumers together because the public implementation/plugin layouts change. The [Etcd Client construction migration](../reference/directory-structure.md#typed-etcd-client-construction) describes optional JSON dependencies and tests without an Etcd service. diff --git a/docs/guides/plugins_CN.md b/docs/guides/plugins_CN.md index b66b700d4..ef5d6defb 100644 --- a/docs/guides/plugins_CN.md +++ b/docs/guides/plugins_CN.md @@ -200,3 +200,5 @@ Net 通过私有工厂返回 `std::unique_ptr`,并启用 Kernel 同样使用类型化构造,并保留公开 `*_impl`、`kernel_plugin` 类型与头路径;私有工厂头不安装。公开类布局发生变化,Kernel 与消费者需要一起重建。[Kernel 迁移表](../reference/kernel_CN.md#kernel-类型化构造)记录保持不变的接口键、update 选择与注册顺序。 Consul 使用相同构造方式,保留全部三个公开头与类型。私有工厂不安装;模块保留接口 RTTI 键和禁用 update 的选择。公开实现/插件布局发生变化,Consul 与消费者必须一起重建。测试入口与依赖条件见 [Consul 构造迁移](../reference/directory-structure_CN.md#consul-类型化构造)。 + +Etcd Client 使用类型化基类、返回接口的私有工厂与显式入口/出口,保留全部五个公开头与类型。接口 RTTI 键和禁用 update 的选择不变。公开实现/插件布局发生变化,插件与消费者必须一起重建。可选 JSON 依赖与无需 Etcd 服务的测试见 [Etcd Client 构造迁移](../reference/directory-structure_CN.md#etcd-client-类型化构造)。 diff --git a/docs/reference/build-options.md b/docs/reference/build-options.md index f6be18c32..1825ad741 100644 --- a/docs/reference/build-options.md +++ b/docs/reference/build-options.md @@ -39,9 +39,9 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction) and [Consul typed construction](directory-structure.md#typed-consul-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. +All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction), [Consul typed construction](directory-structure.md#typed-consul-construction) and [Etcd Client typed construction](directory-structure.md#typed-etcd-client-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. -Applications CI enables the optional Consul plugin on Linux, macOS and Windows. Its `check-core` run includes construction, DLL lifecycle and production export checks; these checks require no Consul service and do not establish service interoperability. +Applications CI explicitly enables Consul and Etcd Client on Linux, macOS and Windows. Its `check-core` run includes their construction, DLL lifecycle and production export checks; these checks require no Consul or Etcd service and do not establish service interoperability. Etcd Client keeps its existing default `ON` selection and can be disabled with `ARK_BUILD_PLUGIN_ETCD_CLIENT=OFF`. Building it requires the existing `nlohmann_json` package (`etcd-client` vcpkg feature); disabling it does not add that JSON dependency to core tests. Link a consumer to `ark::mysql` and/or `ark::redis` for the public headers and usage requirements. These public interface targets do not load a runtime library. Deploy the selected libraries with the launcher and add their stems to the application's manifest: diff --git a/docs/reference/build-options_CN.md b/docs/reference/build-options_CN.md index b57abfb08..d9d1cd082 100644 --- a/docs/reference/build-options_CN.md +++ b/docs/reference/build-options_CN.md @@ -39,9 +39,9 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)与 [Consul 类型化构造](directory-structure_CN.md#consul-类型化构造)改变公开类布局,需要重建消费者。两者的私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 +全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)、[Consul 类型化构造](directory-structure_CN.md#consul-类型化构造)与 [Etcd Client 类型化构造](directory-structure_CN.md#etcd-client-类型化构造)改变公开类布局,需要重建消费者。这些私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 -应用 CI 在 Linux、macOS、Windows 启用可选 Consul 插件,`check-core` 包含构造、动态库生命周期与生产库导出检查。这些检查不需要 Consul 服务,也不代表服务互通验收。 +应用 CI 在 Linux、macOS、Windows 显式启用 Consul 与 Etcd Client,`check-core` 包含两者的构造、动态库生命周期与生产库导出检查。这些检查不需要 Consul 或 Etcd 服务,也不代表服务互通验收。Etcd Client 保留现有默认 `ON` 选择,可通过 `ARK_BUILD_PLUGIN_ETCD_CLIENT=OFF` 关闭。构建它需要现有 `nlohmann_json` 软件包(vcpkg 的 `etcd-client` feature);关闭它不会向核心测试增加该 JSON 依赖。 业务插件链接 `ark::mysql` 和/或 `ark::redis`,以获取公开头文件和编译要求;公共接口目标不会加载运行时动态库。将所需动态库与启动器一起部署,并在应用清单中填写库名称: diff --git a/docs/reference/directory-structure.md b/docs/reference/directory-structure.md index 890acc45d..8689ceae9 100644 --- a/docs/reference/directory-structure.md +++ b/docs/reference/directory-structure.md @@ -180,3 +180,19 @@ cmake --build build --target check-consul-construction --parallel 2 ``` `ARK_BUILD_PLUGIN_CONSUL=ON` also requires `ARK_BUILD_APPLICATIONS=ON` and remains off by default. It links the generated protocol target `ark::proto` directly. With the core tests enabled, `check-consul-plugin` additionally inspects the actual Consul DSO exports in a separate process; `check-core` includes the applicable Consul checks. The controlled factory does not exercise the production HTTP client or a Consul server. These construction checks do not establish live Consul interoperability, protocol acceptance or arbitrary hot-update safety. + +## Typed Etcd Client construction + +**Breaking ABI/layout change:** `etcd_client_module_impl` now inherits `module_base` and `etcd_client_plugin` inherits `plugin_base`. Rebuild Etcd Client and consumers together. All five public headers, their `` paths and public types remain available through `ark::etcd_client`; the `etcd_client_module` and `etcd_client` interfaces are unchanged. C++ plugin boundaries do not promise compiler-independent ABI compatibility. + +The private `etcd_client_factory.hpp` returns `std::unique_ptr` and is excluded from public mappings and installation. Explicit `DllEntryPlugin` / `DllExitPlugin` retain the existing platform exports and logger setup. Registration uses the original interface RTTI key and `module_update::disabled`; unregistration destroys the owned module. HTTP-client lookup, endpoint handling, timeout argument order, lease requests and callbacks are unchanged. The existing `keepalive` stub remains incomplete; this construction change adds no protocol or Registry capability. + +Etcd Client remains selected by default for application builds, with `ARK_BUILD_PLUGIN_ETCD_CLIENT=OFF` available to disable it. Its existing `nlohmann_json` package is required only when building the plugin (`etcd-client` vcpkg feature). With `ARK_BUILD_APPLICATIONS=ON`, `ARK_BUILD_PLUGIN_ETCD_CLIENT=ON`, `ARK_BUILD_PROTO=ON` and `ARK_BUILD_CORE_TESTS=ON`, run: + +```sh +cmake --build build --target check-etcd-client-plugin --parallel 1 +``` + +This target includes `check-etcd-client-construction`: actual factory/assembly doctest cases, plus a controlled-factory DSO using the production entry/exit source to check configuration handoff, interface metadata, disabled updates, exactly-once ownership and repeated stop. A separate process loads the actual production plugin, checks its entry/exit exports and unloads it. `check-core` includes both checks when the runtime plugin is selected; disabled-plugin core builds do not acquire the JSON dependency. Applications CI explicitly enables the plugin and runs these checks through its existing `check-core` job. + +These tests do not run an Etcd service or exercise production HTTP/Etcd interoperability. They establish construction and DSO boundaries, not a completed Registry or arbitrary hot-update safety. diff --git a/docs/reference/directory-structure_CN.md b/docs/reference/directory-structure_CN.md index 18665fdf1..809b09471 100644 --- a/docs/reference/directory-structure_CN.md +++ b/docs/reference/directory-structure_CN.md @@ -170,3 +170,19 @@ cmake --build build --target check-consul-construction --parallel 2 ``` `ARK_BUILD_PLUGIN_CONSUL=ON` 还要求 `ARK_BUILD_APPLICATIONS=ON`,默认保持关闭。插件直接链接生成协议目标 `ark::proto`。开启核心测试后,`check-consul-plugin` 还在独立进程中检查真实 Consul DSO 的导出符号;`check-core` 包含当前条件下可用的 Consul 检查。受控工厂不执行生产 HTTP 客户端或访问 Consul 服务。这些构造检查不代表真实 Consul 互通、协议验收或任意热更新安全。 + +## Etcd Client 类型化构造 + +**ABI/布局不兼容变更:** `etcd_client_module_impl` 现在继承 `module_base`,`etcd_client_plugin` 继承 `plugin_base`。Etcd Client 与消费者必须一起重建。全部五个公开头、`` 路径和公开类型继续通过 `ark::etcd_client` 提供,`etcd_client_module` 与 `etcd_client` 接口不变。C++ 插件边界不承诺跨编译器的 ABI 兼容性。 + +私有 `etcd_client_factory.hpp` 返回 `std::unique_ptr`,不映射、不安装。显式 `DllEntryPlugin`/`DllExitPlugin` 保留原有平台导出和日志初始化。注册沿用原接口 RTTI 键,显式使用 `module_update::disabled`;注销时销毁拥有的模块。HTTP 客户端查找、端点处理、超时参数顺序、租约请求与回调保持不变。原有 `keepalive` 空实现仍未完成,本次构造调整不增加协议或 Registry 能力。 + +Etcd Client 保留应用构建默认选中的策略,可通过 `ARK_BUILD_PLUGIN_ETCD_CLIENT=OFF` 关闭。仅构建该插件时需要现有 `nlohmann_json` 软件包(vcpkg 的 `etcd-client` feature)。开启 `ARK_BUILD_APPLICATIONS=ON`、`ARK_BUILD_PLUGIN_ETCD_CLIENT=ON`、`ARK_BUILD_PROTO=ON` 与 `ARK_BUILD_CORE_TESTS=ON` 后,运行: + +```sh +cmake --build build --target check-etcd-client-plugin --parallel 1 +``` + +该目标包含 `check-etcd-client-construction`:真实工厂/装配的 doctest 用例,以及使用生产入口/出口源码与受控工厂的 DSO 测试,覆盖配置传递、接口元数据、禁用 update、所有权恰好销毁一次和重复 stop。另一个独立进程加载真实生产插件,检查入口/出口导出后卸载。选中运行时插件时,`check-core` 包含这两项检查;关闭插件的核心构建不增加 JSON 依赖。应用 CI 显式启用该插件,通过现有 `check-core` 任务执行检查。 + +这些测试不启动 Etcd 服务,不执行生产 HTTP/Etcd 互通验收,只验证构造与 DSO 边界,不代表 Registry 已完成,也不证明任意热更新安全。 diff --git a/src/plugins/etcd_client/etcd_client_factory.hpp b/src/plugins/etcd_client/etcd_client_factory.hpp new file mode 100644 index 000000000..e521550f8 --- /dev/null +++ b/src/plugins/etcd_client/etcd_client_factory.hpp @@ -0,0 +1,13 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "ark/etcd_client/etcd_client_module.hpp" + +// Plugin-private construction; deliberately absent from public header mappings. +namespace ark::detail { + +std::unique_ptr make_etcd_client_module(); + +} // namespace ark::detail diff --git a/src/plugins/etcd_client/etcd_client_module_impl.cpp b/src/plugins/etcd_client/etcd_client_module_impl.cpp index 7c5007fb1..4edfc4b98 100644 --- a/src/plugins/etcd_client/etcd_client_module_impl.cpp +++ b/src/plugins/etcd_client/etcd_client_module_impl.cpp @@ -1,8 +1,13 @@ #include "ark/etcd_client/etcd_client_impl.hpp" #include "ark/etcd_client/etcd_client_module_impl.hpp" +#include "etcd_client_factory.hpp" namespace ark { +std::unique_ptr detail::make_etcd_client_module() { + return std::make_unique(); +} + bool etcd_client_module_impl::init() { http_client_module_ = find_module(); return http_client_module_ != nullptr; diff --git a/src/plugins/etcd_client/etcd_client_module_impl.hpp b/src/plugins/etcd_client/etcd_client_module_impl.hpp index 88e659ece..8296b0aed 100644 --- a/src/plugins/etcd_client/etcd_client_module_impl.hpp +++ b/src/plugins/etcd_client/etcd_client_module_impl.hpp @@ -1,19 +1,17 @@ #pragma once #include "ark/etcd_client/etcd_client_module.hpp" +#include "ark/module_base.hpp" #include "ark/net/http_client_module.hpp" -#include "ark/plugin_manager.hpp" namespace ark { -class etcd_client_module_impl final : public etcd_client_module +class etcd_client_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS - public: - virtual bool init() override; + bool init() override; - virtual std::shared_ptr create_etcd_client( + std::shared_ptr create_etcd_client( std::vector endpoints, int io_timeout = 5, int connect_timeout = 2) override; private: diff --git a/src/plugins/etcd_client/etcd_client_plugin.cpp b/src/plugins/etcd_client/etcd_client_plugin.cpp index a9f2edf23..ac6ccaef3 100644 --- a/src/plugins/etcd_client/etcd_client_plugin.cpp +++ b/src/plugins/etcd_client/etcd_client_plugin.cpp @@ -1,16 +1,31 @@ -#include "ark/etcd_client/etcd_client_module_impl.hpp" +#include + #include "ark/etcd_client/etcd_client_plugin.hpp" +#include "etcd_client_factory.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(etcd_client_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void etcd_client_plugin::install() { - ARK_REGISTER_MODULE(etcd_client_module, etcd_client_module_impl); + if (!register_module(detail::make_etcd_client_module(), module_update::disabled)) + { + throw std::runtime_error("Etcd Client module registration rejected"); + } } void etcd_client_plugin::uninstall() { - ARK_UNREGISTER_MODULE(etcd_client_module, etcd_client_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("Etcd Client module unregistration rejected"); + } } } // namespace ark diff --git a/src/plugins/etcd_client/etcd_client_plugin.hpp b/src/plugins/etcd_client/etcd_client_plugin.hpp index a199b52fb..684317dbb 100755 --- a/src/plugins/etcd_client/etcd_client_plugin.hpp +++ b/src/plugins/etcd_client/etcd_client_plugin.hpp @@ -1,8 +1,14 @@ -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#pragma once + +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(etcd_client_plugin) +class etcd_client_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark diff --git a/tests/ci/install-package.test.mjs b/tests/ci/install-package.test.mjs index 0b5daeec1..43eb8591d 100644 --- a/tests/ci/install-package.test.mjs +++ b/tests/ci/install-package.test.mjs @@ -72,10 +72,10 @@ include(cmake/ark_install.cmake) const text = readFileSync(file, 'utf8'); for (const forbidden of [root, producer, build, prefix]) assert.ok(!text.includes(forbidden), `${file}: ${forbidden}`); } - for (const name of ['mysql/mysql_module.hpp', 'kernel/kernel_plugin.hpp', 'physic/physic_module_impl.hpp', 'application_manifest.hpp']) { + for (const name of ['mysql/mysql_module.hpp', 'kernel/kernel_plugin.hpp', 'etcd_client/etcd_client_plugin.hpp', 'etcd_client/etcd_client_module_impl.hpp', 'physic/physic_module_impl.hpp', 'application_manifest.hpp']) { assert.ok(existsSync(join(relocated, 'include/ark', name)), name); } - for (const name of ['mysql/mysql_plugin.hpp', 'bus/bus_config_parser.hpp', 'http/http_plugin.hpp', 'net/http_client_module_impl.hpp', 'net/http_client_factory.hpp', 'kernel/kernel_factory.hpp', 'configuration_reader.hpp']) { + for (const name of ['mysql/mysql_plugin.hpp', 'bus/bus_config_parser.hpp', 'http/http_plugin.hpp', 'net/http_client_module_impl.hpp', 'net/http_client_factory.hpp', 'kernel/kernel_factory.hpp', 'etcd_client/etcd_client_factory.hpp', 'configuration_reader.hpp']) { assert.equal(existsSync(join(relocated, 'include/ark', name)), false, name); } rmSync(producer, { recursive: true }); diff --git a/tests/ci/package-dependencies.test.mjs b/tests/ci/package-dependencies.test.mjs index 9fee41e8e..840a16bc9 100644 --- a/tests/ci/package-dependencies.test.mjs +++ b/tests/ci/package-dependencies.test.mjs @@ -87,3 +87,20 @@ test('Applications CI enables Consul construction and production export checks w assert.match(checks, /add_dependencies\(check-core check-consul-plugin\)/); assert.match(checks, /add_dependencies\(check-core check-consul-construction\)/); }); + +test('Applications CI explicitly enables Etcd Client ownership and production DSO checks', () => { + const applications = read('.github/workflows/ci.yml').split(' applications:\n')[1].split('\n database:')[0]; + assert.match(applications, /-DARK_BUILD_PLUGIN_ETCD_CLIENT=ON/); + assert.match(applications, /run: cmake --build ci-build --target check-core/); + const core = read('tests/core/CMakeLists.txt'); + assert.match(core, /if\(TARGET ark_etcd_client_plugin\)[\s\S]*add_subdirectory\(\.\.\/etcd_client/); + const checks = read('tests/etcd_client/CMakeLists.txt'); + assert.match(checks, /add_dependencies\(check-core check-etcd-client-plugin check-etcd-client-construction\)/); + assert.match(checks, /etcd_client_impl\.cpp/); + const mappings = read('src/plugins/CMakeLists.txt').split('set(ark_etcd_client_public_headers')[1].split('\n)')[0]; + assert.doesNotMatch(mappings, /etcd_client_factory/); + const plugin = read('src/plugins/etcd_client/etcd_client_plugin.cpp'); + assert.match(plugin, /DllEntryPlugin/); + assert.match(plugin, /DllExitPlugin/); + assert.doesNotMatch(plugin, /ARK_DECLARE_PLUGIN|ARK_REGISTER_MODULE|ARK_UNREGISTER_MODULE/); +}); diff --git a/tests/consumer/etcd_client/main.cpp b/tests/consumer/etcd_client/main.cpp index ea35b4e3e..8b4071e70 100644 --- a/tests/consumer/etcd_client/main.cpp +++ b/tests/consumer/etcd_client/main.cpp @@ -8,8 +8,12 @@ #include static_assert(std::is_base_of_v); -static_assert(std::is_base_of_v); -static_assert(std::is_base_of_v); +static_assert(std::is_base_of_v, ark::etcd_client_module_impl>); +static_assert(std::is_base_of_v, ark::etcd_client_plugin>); + +#if __has_include() +#error "Etcd Client private factory must not be mapped or installed" +#endif int main() { return 0; diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 768a291bb..ba3267c3f 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -226,6 +226,11 @@ if(ARK_BUILD_PROTO AND ARK_BUILD_CONFIG) add_subdirectory(../consul "${CMAKE_CURRENT_BINARY_DIR}/consul") endif() +# Keep JSON optional: construction checks follow the selected runtime plugin. +if(TARGET ark_etcd_client_plugin) + add_subdirectory(../etcd_client "${CMAKE_CURRENT_BINARY_DIR}/etcd_client") +endif() + if(ARK_BUILD_PROTO AND TARGET ark_kernel_plugin AND TARGET ark_utility_plugin) add_executable(ark_kernel_plugin_tests ../app/kernel_plugin.cpp) set_property(TARGET ark_kernel_plugin_tests PROPERTY LINK_LIBRARIES ark::kernel) diff --git a/tests/etcd_client/CMakeLists.txt b/tests/etcd_client/CMakeLists.txt new file mode 100644 index 000000000..66e391ace --- /dev/null +++ b/tests/etcd_client/CMakeLists.txt @@ -0,0 +1,35 @@ +# Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +find_package(nlohmann_json CONFIG REQUIRED) +set(etcd_client_source "${PROJECT_SOURCE_DIR}/src/plugins/etcd_client") + +# Compile real assembly/factory locally too: private C++ symbols are not DLL exports. +add_executable(ark_etcd_client_construction_tests ../core/main.cpp construction.cpp + "${etcd_client_source}/etcd_client_plugin.cpp" "${etcd_client_source}/etcd_client_module_impl.cpp" "${etcd_client_source}/etcd_client_impl.cpp") +set_property(TARGET ark_etcd_client_construction_tests PROPERTY LINK_LIBRARIES + ark::etcd_client nlohmann_json::nlohmann_json) +target_include_directories(ark_etcd_client_construction_tests PRIVATE "${etcd_client_source}") + +# A controlled module exercises the actual entry/exit functions without HTTP or Etcd Client services. +add_library(ark_etcd_client_entry_probe SHARED entry_factory.cpp "${etcd_client_source}/etcd_client_plugin.cpp") +set_property(TARGET ark_etcd_client_entry_probe PROPERTY LINK_LIBRARIES ark::etcd_client) +target_include_directories(ark_etcd_client_entry_probe PRIVATE "${etcd_client_source}") +set_target_properties(ark_etcd_client_entry_probe PROPERTIES PREFIX "") +add_executable(ark_etcd_client_entry_tests entry.cpp) +set_property(TARGET ark_etcd_client_entry_tests PROPERTY LINK_LIBRARIES ark::etcd_client) + +add_custom_target(check-etcd-client-construction + COMMAND $ + COMMAND $ "$/" "ark_etcd_client_entry_probe" + DEPENDS ark_etcd_client_construction_tests ark_etcd_client_entry_tests ark_etcd_client_entry_probe + USES_TERMINAL VERBATIM) + +if(TARGET ark_etcd_client_plugin) + # Keep real DSO export inspection separate from the host linking static AFProto. + add_executable(ark_etcd_client_exports_tests exports.cpp) + set_property(TARGET ark_etcd_client_exports_tests PROPERTY LINK_LIBRARIES ark::core) + add_custom_target(check-etcd-client-plugin + COMMAND $ "$/" "ark_etcd_client_plugin" + DEPENDS ark_etcd_client_exports_tests ark_etcd_client_plugin check-etcd-client-construction + USES_TERMINAL VERBATIM) + add_dependencies(check-core check-etcd-client-plugin check-etcd-client-construction) +endif() diff --git a/tests/etcd_client/construction.cpp b/tests/etcd_client/construction.cpp new file mode 100644 index 000000000..5d600fca3 --- /dev/null +++ b/tests/etcd_client/construction.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include "../network/doctest.h" + +#include +#include +#include "etcd_client_factory.hpp" + +#if __has_include() +#error "Etcd Client public mappings must exclude the private factory" +#endif + +static_assert(std::is_base_of_v, ark::etcd_client_module_impl>); +static_assert(std::is_base_of_v, ark::etcd_client_plugin>); +static_assert(std::is_same_v); +static_assert( + std::is_same_v>); + +TEST_CASE("Etcd Client real factory retains its public implementation and unpublished metadata") { + auto candidate = ark::detail::make_etcd_client_module(); + REQUIRE(candidate != nullptr); + const auto* instance = candidate.get(); + CHECK(typeid(*instance) == typeid(ark::etcd_client_module_impl)); + CHECK(candidate->get_plugin_manager() == nullptr); + CHECK(candidate->get_name().empty()); +} + +TEST_CASE("Etcd Client assembly retains the interface key, disabled updates and explicit ownership") { + ark::plugin_manager manager; + ark::etcd_client_plugin owner; + owner.set_plugin_manager(&manager); + CHECK(owner.get_plugin_version() == 0); + CHECK(owner.get_plugin_name() == typeid(ark::etcd_client_plugin).name()); + owner.set_plugin_conf("etcd-client-construction.toml"); + CHECK(ark::etcd_client_plugin::get_plugin_conf() == "etcd-client-construction.toml"); + owner.install(); + auto* instance = manager.find_module(); + REQUIRE(instance != nullptr); + CHECK(typeid(*instance) == typeid(ark::etcd_client_module_impl)); + CHECK(instance->get_plugin_manager() == &manager); + CHECK(instance->get_name() == typeid(ark::etcd_client_module).name()); + // Insertion succeeds only when assembly left the module out of the update set. + CHECK(manager.add_update_module(instance)); + manager.remove_update_module(instance->get_name()); + CHECK_THROWS_AS(owner.install(), std::runtime_error); + CHECK(manager.find_module() == instance); + owner.uninstall(); + // Reinstallation proves the public registration key and ownership were released. + CHECK_NOTHROW(owner.install()); + CHECK_NOTHROW(owner.uninstall()); + CHECK(manager.update(1)); + CHECK_THROWS_AS(owner.uninstall(), std::runtime_error); +} diff --git a/tests/etcd_client/entry.cpp b/tests/etcd_client/entry.cpp new file mode 100644 index 000000000..7420feeca --- /dev/null +++ b/tests/etcd_client/entry.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include +#include +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected Etcd Client entry-probe directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load Etcd Client entry-probe library"); + auto events = reinterpret_cast(library.get_symbol("etcd_client_entry_events")); + require(events && events(0) == 0 && events(1) == 0, "Entry-probe state is not fresh"); + require(library.get_symbol("DllEntryPlugin") && library.get_symbol("DllExitPlugin"), + "Etcd Client explicit entry/exit exports are missing"); + ark::plugin_manager manager; + require(manager.start(0, argv[1], "conf/", {{argv[2], "entry-probe.toml"}}), "Etcd Client explicit entry failed"); + auto* instance = manager.find_module(); + require(instance && instance->get_plugin_manager() == &manager && + instance->get_name() == typeid(ark::etcd_client_module).name(), + "Etcd Client explicit entry lost interface registration metadata"); + require(events(0) == 1 && events(1) == 0, "Etcd Client entry did not transfer factory ownership"); + require(manager.update(1) && events(2) == 0, "Etcd Client entry changed disabled update selection"); + require(manager.stop(2) && events(1) == 1, "Etcd Client explicit exit did not destroy its module exactly once"); + require(manager.stop(3) && events(1) == 1, "Repeated stop invoked Etcd Client exit twice"); + require(library.unload(), "Cannot release Etcd Client observation handle"); + std::cout << "Etcd Client real assembly entry/exit, configuration, interface metadata and ownership passed\n"; +} diff --git a/tests/etcd_client/entry_factory.cpp b/tests/etcd_client/entry_factory.cpp new file mode 100644 index 000000000..ab69b24a0 --- /dev/null +++ b/tests/etcd_client/entry_factory.cpp @@ -0,0 +1,59 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include + +#include +#include +#include "etcd_client_factory.hpp" + +namespace { + +unsigned created{}; +unsigned destroyed{}; +unsigned updated{}; + +class entry_etcd_client final : public ark::module_base +{ +public: + entry_etcd_client() { + ++created; + } + + ~entry_etcd_client() override { + ++destroyed; + } + + bool init() override { + return ark::etcd_client_plugin::get_plugin_conf() == "conf/entry-probe.toml"; + } + + bool update() override { + ++updated; + return true; + } + + std::shared_ptr create_etcd_client(std::vector, int, int) override { + return {}; + } +}; + +} // namespace + +namespace ark::detail { + +std::unique_ptr make_etcd_client_module() { + return std::make_unique(); +} + +} // namespace ark::detail + +ARK_EXPORT_FUNC unsigned etcd_client_entry_events(unsigned event) { + switch (event) + { + case 0: + return created; + case 1: + return destroyed; + default: + return updated; + } +} diff --git a/tests/etcd_client/exports.cpp b/tests/etcd_client/exports.cpp new file mode 100644 index 000000000..f6ef4cb20 --- /dev/null +++ b/tests/etcd_client/exports.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected plugin directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load actual Etcd Client shared library"); + require(library.get_symbol("DllEntryPlugin") != nullptr, "Missing explicit DllEntryPlugin export"); + require(library.get_symbol("DllExitPlugin") != nullptr, "Missing explicit DllExitPlugin export"); + require(library.unload(), "Cannot unload Etcd Client shared library"); + std::cout << argv[2] << ": actual DSO load, explicit entry/exit exports and unload passed\n"; +} From d38982c3cea351c07d51a8d634be0dd0aa217c73 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 13:01:32 +0800 Subject: [PATCH 11/13] fix(tests): probe Net DSO listener and peer closure Check a successful live-listener negative control, retain the exercised TCP peer across manager shutdown, and require a remote EOF or reset before stopping that peer. Check listener closure using an exact refused new connection instead of global immediate port rebinding. Keep existing endpoint ownership, manager state, main-thread callbacks and pending HTTP cancellation assertions. The original macOS CI bind conflict holder was not reproduced locally; no production shutdown or reuse-address behavior changes. --- tests/app/net_plugin.cpp | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/tests/app/net_plugin.cpp b/tests/app/net_plugin.cpp index 6eb53f4a3..48a998c83 100644 --- a/tests/app/net_plugin.cpp +++ b/tests/app/net_plugin.cpp @@ -37,6 +37,13 @@ void wait_for(predicate ready, pump update) { require(ready(), "Timed out waiting for owned loopback peer"); } +arknet::error_code listener_probe(asio::io_context& context, const asio::ip::tcp::endpoint& endpoint) { + asio::ip::tcp::socket probe(context); + arknet::error_code error; + probe.connect(endpoint, error); + return error; +} + struct receiver { void on_message(const ark::net_msg* message, ark::conv_id_t session) { @@ -86,8 +93,17 @@ int main(int argc, char* argv[]) { receiver received; require(server->reg_msg_callback(45000, &received, &receiver::on_message), "Net message callback rejected"); std::weak_ptr endpoint = server->get_net(); + const asio::ip::tcp::endpoint listener_endpoint(asio::ip::make_address("127.0.0.1"), port); + const auto live_probe = listener_probe(context, listener_endpoint); + require(!live_probe, "Live Net listener negative control failed: " + live_probe.message()); + std::atomic peer_closed{}; + arknet::error_code peer_close_error; arknet::tcp_client client; client.set_auto_reconnect(false); + client.bind_disconnect([&] { + peer_close_error = arknet::get_last_error(); + peer_closed.store(true); + }); require(client.start("127.0.0.1", port), "Owned TCP peer failed to start"); ark::s_msg_header header; header.set_message_id(45000); @@ -143,16 +159,27 @@ int main(int argc, char* argv[]) { const auto cancelled = pending.get(); require(!cancelled.first && cancelled.second == arknet::error_code(asio::error::operation_aborted).message(), "HTTP DSO shutdown changed pending-request cancellation"); - client.stop(); peer.stop(); + require(client.is_started() && !peer_closed.load(), "Owned TCP peer must remain live across Net shutdown"); server.reset(); require(manager.stop(++tick), "Net DSO shutdown did not complete"); require(endpoint.expired(), "Net listener survived normal module unregistration"); require(manager.get_shutdown_state() == ark::plugin_manager::shutdown_state::stopped, "Net resources were retained instead of normally stopped"); + // Keep the peer open until the server closes its accepted socket. Stopping + // the client first would hide a retained server-side connection. + wait_for([&] { return peer_closed.load(); }, [] {}); + require(peer_close_error == asio::error::eof || peer_close_error == asio::error::connection_reset, + "Net shutdown did not remotely close the owned TCP peer: " + peer_close_error.message()); + client.stop(); require(manager.stop(++tick), "Repeated Net shutdown failed"); - asio::ip::tcp::acceptor rebound(context, {asio::ip::make_address("127.0.0.1"), port}); - require(rebound.is_open(), "Net shutdown did not release its listener"); - std::cout << "Net actual DSOs: interface RTTI lookup, manager-owned TCP/HTTP callbacks, listener release and " + // Binding the same port also tests unrelated kernel port-reuse state. A + // refused new connection checks listener closure directly, with no retry. + // The successful probe above is the negative control for a live listener. + const auto closed_probe = listener_probe(context, listener_endpoint); + require(closed_probe == asio::error::connection_refused, + "Net listener closure probe must be refused: " + closed_probe.message()); + std::cout << "Net actual DSOs: interface RTTI lookup, manager-owned TCP/HTTP callbacks, listener refusal, remote " + "peer closure and " "normal repeated shutdown passed\n"; } From 5c621eb117923068d1e220751cda19fdaf48bb80 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 13:09:43 +0800 Subject: [PATCH 12/13] fix(tests): bound the Net listener shutdown probe Use a private io_context, async_connect and the existing five-second deadline. Cancel and close the probe socket on expiry, return typed timed_out rather than operation_aborted, and drain connect and timer callbacks before returning. Keep all live-listener, retained-peer and owner-state assertions and the existing test timeout. Production shutdown remains unchanged. --- tests/app/net_plugin.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/tests/app/net_plugin.cpp b/tests/app/net_plugin.cpp index 48a998c83..e89307883 100644 --- a/tests/app/net_plugin.cpp +++ b/tests/app/net_plugin.cpp @@ -37,11 +37,31 @@ void wait_for(predicate ready, pump update) { require(ready(), "Timed out waiting for owned loopback peer"); } -arknet::error_code listener_probe(asio::io_context& context, const asio::ip::tcp::endpoint& endpoint) { +arknet::error_code listener_probe(asio::io_context&, const asio::ip::tcp::endpoint& endpoint) { + asio::io_context context; asio::ip::tcp::socket probe(context); - arknet::error_code error; - probe.connect(endpoint, error); - return error; + asio::steady_timer deadline(context, 5s); + arknet::error_code result = asio::error::operation_aborted; + bool completed = false; + bool timed_out = false; + deadline.async_wait([&](const arknet::error_code& error) { + if (!error && !completed) + { + timed_out = true; + arknet::error_code ignored; + probe.cancel(ignored); + probe.close(ignored); + } + }); + probe.async_connect(endpoint, [&](const arknet::error_code& error) { + completed = true; + result = timed_out ? arknet::error_code(asio::error::timed_out) : error; + deadline.cancel(); + }); + // Drain both the connect completion and the cancelled timer before their + // stack captures go out of scope. A deadline is not a refused connection. + context.run(); + return result; } struct receiver From ab1a01c24ed8af1a6403af05ce64cdafc8020a54 Mon Sep 17 00:00:00 2001 From: nick Date: Mon, 26 Oct 2026 13:53:29 +0800 Subject: [PATCH 13/13] refactor(osssdk)!: use typed plugin construction Retain public OSS SDK headers and types while replacing creation macros with module_base, an interface-returning private factory, plugin_base and explicit entry points. Replace the undefined deregistration call with typed owned unregistration while preserving the interface key and disabled updates. Add real-factory assembly and controlled DSO lifecycle regressions, production export checks and bilingual migration guidance. Keep the optional plugin off by default and retain its TODO capability boundary. BREAKING CHANGE: oss_sdk_module_impl and oss_sdk_plugin change public class layouts. Rebuild OSS SDK and all affected consumers together; original public header paths, types and the oss_sdk_module interface remain available. --- docs/guides/plugins.md | 2 + docs/guides/plugins_CN.md | 2 + docs/project-layout.md | 2 + docs/project-layout_CN.md | 2 + docs/reference/build-options.md | 4 +- docs/reference/build-options_CN.md | 4 +- docs/reference/directory-structure.md | 22 +++++++++ docs/reference/directory-structure_CN.md | 22 +++++++++ src/plugins/README.md | 4 +- src/plugins/README_CN.md | 4 +- src/plugins/osssdk/oss_sdk_factory.hpp | 13 +++++ src/plugins/osssdk/oss_sdk_module_impl.cpp | 5 +- src/plugins/osssdk/oss_sdk_module_impl.hpp | 5 +- src/plugins/osssdk/oss_sdk_plugin.cpp | 25 +++++++--- src/plugins/osssdk/oss_sdk_plugin.hpp | 10 ++-- tests/core/CMakeLists.txt | 2 + tests/osssdk/CMakeLists.txt | 36 ++++++++++++++ tests/osssdk/construction.cpp | 55 ++++++++++++++++++++++ tests/osssdk/entry.cpp | 45 ++++++++++++++++++ tests/osssdk/entry_factory.cpp | 55 ++++++++++++++++++++++ tests/osssdk/exports.cpp | 31 ++++++++++++ 21 files changed, 333 insertions(+), 17 deletions(-) create mode 100644 src/plugins/osssdk/oss_sdk_factory.hpp create mode 100644 tests/osssdk/CMakeLists.txt create mode 100644 tests/osssdk/construction.cpp create mode 100644 tests/osssdk/entry.cpp create mode 100644 tests/osssdk/entry_factory.cpp create mode 100644 tests/osssdk/exports.cpp diff --git a/docs/guides/plugins.md b/docs/guides/plugins.md index 7afc46cf7..e6b8d8bd2 100644 --- a/docs/guides/plugins.md +++ b/docs/guides/plugins.md @@ -215,3 +215,5 @@ Kernel also uses typed construction while retaining its public `*_impl` and `ker Consul follows the same construction pattern while retaining all three public headers and types. Its private factory is not installed; the module keeps its interface RTTI key and disabled updates. Rebuild Consul and consumers together because its public implementation/plugin layouts change. See [Consul construction migration](../reference/directory-structure.md#typed-consul-construction) for test entry points and dependency conditions. Etcd Client also retains all five public headers and types while using typed bases, a private interface-returning factory and explicit entry/exit functions. Its interface RTTI key and disabled updates are unchanged. Rebuild the plugin and consumers together because the public implementation/plugin layouts change. The [Etcd Client construction migration](../reference/directory-structure.md#typed-etcd-client-construction) describes optional JSON dependencies and tests without an Etcd service. + +OSS SDK uses the same typed bases and private interface-returning factory while retaining all three public headers/types, its interface RTTI key and disabled updates. Rebuild the plugin and consumers together for the public layout change. Typed unregistration replaces its undefined legacy deregistration call; the implementation remains a TODO scaffold. See [OSS SDK construction migration](../reference/directory-structure.md#typed-oss-sdk-construction) for build and test boundaries. diff --git a/docs/guides/plugins_CN.md b/docs/guides/plugins_CN.md index ef5d6defb..9f9a2126b 100644 --- a/docs/guides/plugins_CN.md +++ b/docs/guides/plugins_CN.md @@ -202,3 +202,5 @@ Kernel 同样使用类型化构造,并保留公开 `*_impl`、`kernel_plugin` Consul 使用相同构造方式,保留全部三个公开头与类型。私有工厂不安装;模块保留接口 RTTI 键和禁用 update 的选择。公开实现/插件布局发生变化,Consul 与消费者必须一起重建。测试入口与依赖条件见 [Consul 构造迁移](../reference/directory-structure_CN.md#consul-类型化构造)。 Etcd Client 使用类型化基类、返回接口的私有工厂与显式入口/出口,保留全部五个公开头与类型。接口 RTTI 键和禁用 update 的选择不变。公开实现/插件布局发生变化,插件与消费者必须一起重建。可选 JSON 依赖与无需 Etcd 服务的测试见 [Etcd Client 构造迁移](../reference/directory-structure_CN.md#etcd-client-类型化构造)。 + +OSS SDK 使用相同的类型化基类和返回接口的私有工厂,保留全部三个公开头/类型、接口 RTTI 键和禁用 update 的选择。公开布局发生变化,插件与消费者必须一起重建。类型化注销替代未定义的旧注销调用;实现仍为 TODO 骨架。构建与测试边界见 [OSS SDK 构造迁移](../reference/directory-structure_CN.md#oss-sdk-类型化构造)。 diff --git a/docs/project-layout.md b/docs/project-layout.md index 6ce9b31b3..b17dafa02 100644 --- a/docs/project-layout.md +++ b/docs/project-layout.md @@ -133,4 +133,6 @@ cmake -S tests/consumer/etcd_client -B etcd-client-headers -G Ninja \ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel 2 ``` +OSS SDK retains its three public headers and types through typed construction; rebuild the plugin and consumers together because public class layouts change. See [OSS SDK construction migration](reference/directory-structure.md#typed-oss-sdk-construction) for optional build conditions and construction/export checks. The implementation remains a TODO scaffold. + Header mapping does not establish Consul, Kafka or OSS SDK runtime compatibility. Physic and `physics_scene` still require the unsupported legacy PhysX 3 integration and retain their explicit configuration failure; no passing PhysX build is claimed. diff --git a/docs/project-layout_CN.md b/docs/project-layout_CN.md index 82a8ee230..00faadbad 100644 --- a/docs/project-layout_CN.md +++ b/docs/project-layout_CN.md @@ -123,4 +123,6 @@ cmake -S tests/consumer/etcd_client -B etcd-client-headers -G Ninja \ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel 2 ``` +OSS SDK 使用类型化构造,保留全部三个公开头和类型;公开类布局发生变化,插件与消费者必须一起重建。可选构建条件及构造/导出检查见 [OSS SDK 构造迁移](reference/directory-structure_CN.md#oss-sdk-类型化构造)。实现仍为 TODO 骨架。 + 头文件映射检查不代表 Consul、Kafka、OSS SDK 的运行时兼容性已经验证。Physic 与 `physics_scene` 仍依赖未受支持的旧版 PhysX 3 集成,保留明确的配置失败;此处没有宣称 PhysX 构建通过。 diff --git a/docs/reference/build-options.md b/docs/reference/build-options.md index 1825ad741..465a54877 100644 --- a/docs/reference/build-options.md +++ b/docs/reference/build-options.md @@ -39,10 +39,12 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction), [Consul typed construction](directory-structure.md#typed-consul-construction) and [Etcd Client typed construction](directory-structure.md#typed-etcd-client-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. +All official plugin public headers are copied from explicit lists at configuration time into each build tree's generated plugin header mapping. Each `ark::` API target carries that include root even with runtime plugins disabled. MySQL, Redis, Utility, Net, Bus and HTTP exclude private assembly and factories. Kernel retains all 38 existing headers, including `kernel_plugin.hpp`; Consul, Etcd Client, Kafka, OSS SDK and Physic retain all 22 existing public headers, including implementation and assembly headers. Registration keys, public type visibility and optional plugin policies are retained; [Kernel typed construction](kernel.md#typed-kernel-construction), [Consul typed construction](directory-structure.md#typed-consul-construction), [Etcd Client typed construction](directory-structure.md#typed-etcd-client-construction) and [OSS SDK typed construction](directory-structure.md#typed-oss-sdk-construction) change public class layouts and require rebuilding consumers. Their private factory headers are excluded from mapping and installation; header mapping does not validate optional driver integrations. Each public header has one co-located source; its generated include mapping belongs to the selected build tree, rather than a second source layout. Applications CI explicitly enables Consul and Etcd Client on Linux, macOS and Windows. Its `check-core` run includes their construction, DLL lifecycle and production export checks; these checks require no Consul or Etcd service and do not establish service interoperability. Etcd Client keeps its existing default `ON` selection and can be disabled with `ARK_BUILD_PLUGIN_ETCD_CLIENT=OFF`. Building it requires the existing `nlohmann_json` package (`etcd-client` vcpkg feature); disabling it does not add that JSON dependency to core tests. +OSS SDK remains optional and off by default (`ARK_BUILD_PLUGIN_OSSSDK=OFF`). Core tests include its real factory/assembly and controlled DSO checks without Protobuf or a runtime plugin. With the runtime plugin selected, `check-oss-sdk-plugin` also loads the actual DSO and checks its exports; `check-core` includes the applicable checks. The implementation is still a TODO scaffold, with no object-storage client or service acceptance. See [OSS SDK construction migration](directory-structure.md#typed-oss-sdk-construction). + Link a consumer to `ark::mysql` and/or `ark::redis` for the public headers and usage requirements. These public interface targets do not load a runtime library. Deploy the selected libraries with the launcher and add their stems to the application's manifest: ```toml diff --git a/docs/reference/build-options_CN.md b/docs/reference/build-options_CN.md index d9d1cd082..2eec5a7a1 100644 --- a/docs/reference/build-options_CN.md +++ b/docs/reference/build-options_CN.md @@ -39,10 +39,12 @@ cmake -S . -B build -DARK_BUILD_APPLICATIONS=ON \ cmake --build build --target ark_mysql_plugin ark_redis_plugin ``` -全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)、[Consul 类型化构造](directory-structure_CN.md#consul-类型化构造)与 [Etcd Client 类型化构造](directory-structure_CN.md#etcd-client-类型化构造)改变公开类布局,需要重建消费者。这些私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 +全部官方插件的公开头通过显式清单在配置阶段复制到各构建树生成的插件公开头映射。各 `ark::` API target 传递该目录,插件关闭时仍可使用。MySQL、Redis、Utility、Net、Bus、HTTP 的私有装配头和工厂不进入映射;Kernel 保留包括 `kernel_plugin.hpp` 在内的原有 38 个头;Consul、Etcd Client、Kafka、OSS SDK、Physic 保留包括实现头与装配头在内的原有 22 个公开头。注册键、公开类型可见性和可选插件策略继续保留;[Kernel 类型化构造](kernel_CN.md#kernel-类型化构造)、[Consul 类型化构造](directory-structure_CN.md#consul-类型化构造)、[Etcd Client 类型化构造](directory-structure_CN.md#etcd-client-类型化构造)与 [OSS SDK 类型化构造](directory-structure_CN.md#oss-sdk-类型化构造)改变公开类布局,需要重建消费者。这些私有工厂头均不映射、不安装;头文件映射不代表可选驱动集成已经验证。每个公共头仅有一份共置源码;生成的头文件映射属于选定构建树,不构成第二棵源码目录。 应用 CI 在 Linux、macOS、Windows 显式启用 Consul 与 Etcd Client,`check-core` 包含两者的构造、动态库生命周期与生产库导出检查。这些检查不需要 Consul 或 Etcd 服务,也不代表服务互通验收。Etcd Client 保留现有默认 `ON` 选择,可通过 `ARK_BUILD_PLUGIN_ETCD_CLIENT=OFF` 关闭。构建它需要现有 `nlohmann_json` 软件包(vcpkg 的 `etcd-client` feature);关闭它不会向核心测试增加该 JSON 依赖。 +OSS SDK 保持可选且默认关闭(`ARK_BUILD_PLUGIN_OSSSDK=OFF`)。核心测试包含真实工厂/装配与受控 DSO 检查,无需 Protobuf 或运行时插件。选中运行时插件后,`check-oss-sdk-plugin` 还加载真实 DSO 并检查导出;`check-core` 包含当前条件下可用的检查。实现仍为 TODO 骨架,没有对象存储客户端或服务验收。参见 [OSS SDK 构造迁移](directory-structure_CN.md#oss-sdk-类型化构造)。 + 业务插件链接 `ark::mysql` 和/或 `ark::redis`,以获取公开头文件和编译要求;公共接口目标不会加载运行时动态库。将所需动态库与启动器一起部署,并在应用清单中填写库名称: ```toml diff --git a/docs/reference/directory-structure.md b/docs/reference/directory-structure.md index 56ac10a5c..bf3360351 100644 --- a/docs/reference/directory-structure.md +++ b/docs/reference/directory-structure.md @@ -152,6 +152,8 @@ cmake -S tests/consumer/etcd_client -B etcd-client-headers -G Ninja \ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel 2 ``` +OSS SDK retains its three public headers and types through typed construction; rebuild the plugin and consumers together because public class layouts change. See [OSS SDK construction migration](directory-structure.md#typed-oss-sdk-construction) for optional build conditions and construction/export checks. The implementation remains a TODO scaffold. + Header mapping does not establish Consul, Kafka or OSS SDK runtime compatibility. Physic and `physics_scene` still require the unsupported legacy PhysX 3 integration and retain their explicit configuration failure; no passing PhysX build is claimed. @@ -196,3 +198,23 @@ cmake --build build --target check-etcd-client-plugin --parallel 1 This target includes `check-etcd-client-construction`: actual factory/assembly doctest cases, plus a controlled-factory DSO using the production entry/exit source to check configuration handoff, interface metadata, disabled updates, exactly-once ownership and repeated stop. A separate process loads the actual production plugin, checks its entry/exit exports and unloads it. `check-core` includes both checks when the runtime plugin is selected; disabled-plugin core builds do not acquire the JSON dependency. Applications CI explicitly enables the plugin and runs these checks through its existing `check-core` job. These tests do not run an Etcd service or exercise production HTTP/Etcd interoperability. They establish construction and DSO boundaries, not a completed Registry or arbitrary hot-update safety. + +## Typed OSS SDK construction + +**Breaking ABI/layout change:** `oss_sdk_module_impl` now inherits `module_base` and `oss_sdk_plugin` inherits `plugin_base`. Rebuild OSS SDK and consumers together. All three public headers, their `` paths and public types remain available through `ark::osssdk`; the `oss_sdk_module` interface is unchanged. The C++ plugin boundary does not promise compiler-independent ABI compatibility. + +The private `oss_sdk_factory.hpp` returns `std::unique_ptr` and is excluded from public mappings and installation. Explicit `DllEntryPlugin` / `DllExitPlugin` retain platform exports and logger initialization. Installation retains the original `oss_sdk_module` interface RTTI key and disabled updates. Typed unregistration replaces the undefined `ARK_DEREGISTER_MODULE` invocation and destroys the owned module. Configuration storage and the inherited default update method are preserved. The implementation remains a TODO scaffold: no OSS client, request API or object-storage protocol is added. + +With `ARK_BUILD_CORE_TESTS=ON`, `check-oss-sdk-construction` compiles the real factory and assembly into doctest cases and uses the production entry/exit source with a counted controlled factory in a DSO. Checks cover retained public bases/types, factory metadata, interface registration, duplicate rejection, disabled updates, configuration handoff, exactly-once destruction and repeated stop. This target requires only the normal core/arknet dependencies; it needs no runtime plugin, Protobuf, JSON package or object-storage service: + +```sh +cmake --build build --target check-oss-sdk-construction --parallel 1 +``` + +The runtime plugin remains off by default. Enable `ARK_BUILD_APPLICATIONS=ON`, `ARK_BUILD_PLUGIN_OSSSDK=ON`, `ARK_BUILD_PROTO=ON` and `ARK_BUILD_CORE_TESTS=ON` to additionally build the actual `ark_oss_sdk_plugin` DSO and inspect its entry/exit exports in a separate process: + +```sh +cmake --build build --target check-oss-sdk-plugin --parallel 1 +``` + +Application builds retain their existing Protobuf, configuration and Boost.Asio requirements. `check-core` includes the applicable OSS SDK construction/export checks. No external OSS SDK dependency is introduced. These tests establish construction and DSO boundaries only; they do not establish OSS client functionality, service interoperability or arbitrary hot-update safety. diff --git a/docs/reference/directory-structure_CN.md b/docs/reference/directory-structure_CN.md index 25d79a69f..27b49cf93 100644 --- a/docs/reference/directory-structure_CN.md +++ b/docs/reference/directory-structure_CN.md @@ -142,6 +142,8 @@ cmake -S tests/consumer/etcd_client -B etcd-client-headers -G Ninja \ cmake --build etcd-client-headers --target check-etcd-client-headers --parallel 2 ``` +OSS SDK 使用类型化构造,保留全部三个公开头和类型;公开类布局发生变化,插件与消费者必须一起重建。可选构建条件及构造/导出检查见 [OSS SDK 构造迁移](directory-structure_CN.md#oss-sdk-类型化构造)。实现仍为 TODO 骨架。 + 头文件映射检查不代表 Consul、Kafka、OSS SDK 的运行时兼容性已经验证。Physic 与 `physics_scene` 仍依赖未受支持的旧版 PhysX 3 集成,保留明确的配置失败;此处没有宣称 PhysX 构建通过。 ## 安装目录 @@ -186,3 +188,23 @@ cmake --build build --target check-etcd-client-plugin --parallel 1 该目标包含 `check-etcd-client-construction`:真实工厂/装配的 doctest 用例,以及使用生产入口/出口源码与受控工厂的 DSO 测试,覆盖配置传递、接口元数据、禁用 update、所有权恰好销毁一次和重复 stop。另一个独立进程加载真实生产插件,检查入口/出口导出后卸载。选中运行时插件时,`check-core` 包含这两项检查;关闭插件的核心构建不增加 JSON 依赖。应用 CI 显式启用该插件,通过现有 `check-core` 任务执行检查。 这些测试不启动 Etcd 服务,不执行生产 HTTP/Etcd 互通验收,只验证构造与 DSO 边界,不代表 Registry 已完成,也不证明任意热更新安全。 + +## OSS SDK 类型化构造 + +**ABI/布局不兼容变更:** `oss_sdk_module_impl` 现在继承 `module_base`,`oss_sdk_plugin` 继承 `plugin_base`。OSS SDK 与消费者必须一起重建。全部三个公开头、`` 路径和公开类型继续通过 `ark::osssdk` 提供,`oss_sdk_module` 接口不变。C++ 插件边界不承诺跨编译器的 ABI 兼容性。 + +私有 `oss_sdk_factory.hpp` 返回 `std::unique_ptr`,不映射、不安装。显式 `DllEntryPlugin`/`DllExitPlugin` 保留平台导出和日志初始化。安装时沿用 `oss_sdk_module` 接口 RTTI 键,继续禁用 update。类型化注销替代未定义的 `ARK_DEREGISTER_MODULE` 调用,销毁拥有的模块。配置存储和继承的默认 update 方法保持不变。实现仍为 TODO 骨架,不增加 OSS 客户端、请求 API 或对象存储协议。 + +开启 `ARK_BUILD_CORE_TESTS=ON` 后,`check-oss-sdk-construction` 将真实工厂与装配编入 doctest 用例,并使用生产入口/出口源码与带计数的受控工厂构建 DSO。检查覆盖保留的公开基类/类型、工厂元数据、接口注册、重复注册拒绝、禁用 update、配置传递、恰好销毁一次和重复 stop。该目标仅需要通常的 core/arknet 依赖,无需运行时插件、Protobuf、JSON 软件包或对象存储服务: + +```sh +cmake --build build --target check-oss-sdk-construction --parallel 1 +``` + +运行时插件默认保持关闭。开启 `ARK_BUILD_APPLICATIONS=ON`、`ARK_BUILD_PLUGIN_OSSSDK=ON`、`ARK_BUILD_PROTO=ON` 与 `ARK_BUILD_CORE_TESTS=ON` 后,还可构建真实 `ark_oss_sdk_plugin` DSO,并在独立进程中检查入口/出口导出: + +```sh +cmake --build build --target check-oss-sdk-plugin --parallel 1 +``` + +应用构建仍需要现有 Protobuf、配置模块与 Boost.Asio。`check-core` 包含当前条件下可用的 OSS SDK 构造/导出检查,不增加外部 OSS SDK 依赖。这些测试只验证构造与 DSO 边界,不代表 OSS 客户端功能、服务互通或任意热更新安全已经通过验收。 diff --git a/src/plugins/README.md b/src/plugins/README.md index bb41192b6..24ba933db 100755 --- a/src/plugins/README.md +++ b/src/plugins/README.md @@ -28,4 +28,6 @@ Kernel co-locates its 38 existing headers with its cpp files in `src/plugins/ker Consul retains its three public headers and implementation/plugin types while using typed construction, a private owning factory and explicit entry points. Rebuild Consul and consumers together because the public class layouts change. The single interface registration key, disabled updates and request behavior are preserved. See [Consul construction migration](../../docs/reference/directory-structure.md#typed-consul-construction) for build conditions and service-free construction checks. -The other four legacy plugin families retain 19 byte-identical headers. Mapping checks do not validate Consul, Kafka or OSS SDK runtime integrations. Physic retains the unsupported PhysX 3 configuration failure; see [optional legacy dependencies](../../docs/dependencies.md#optional-legacy-integrations). +Kafka and Physic retain their original public headers. Mapping checks do not validate Consul, Kafka or OSS SDK runtime integrations. Physic retains the unsupported PhysX 3 configuration failure; see [optional legacy dependencies](../../docs/dependencies.md#optional-legacy-integrations). + +OSS SDK retains all three public headers and types while using typed construction with a private factory and explicit entry points. Rebuild the plugin and consumers together for the public layout change. It remains off by default and retains its TODO scaffold; construction/export checks add no object-storage client capability. See [OSS SDK construction migration](../../docs/reference/directory-structure.md#typed-oss-sdk-construction). diff --git a/src/plugins/README_CN.md b/src/plugins/README_CN.md index bed2c6eb9..97e8ded49 100644 --- a/src/plugins/README_CN.md +++ b/src/plugins/README_CN.md @@ -28,4 +28,6 @@ Kernel 的原有 38 个头与 cpp 共置于 `src/plugins/kernel/`,通过 `ark: Consul 保留原有三个公开头及实现/插件类型,同时使用类型化构造、私有拥有型工厂和显式入口。公开类布局发生变化,Consul 与消费者必须一起重建。唯一接口注册键、禁用 update 与请求行为保持不变。构建条件及无需服务的构造检查见 [Consul 构造迁移](../../docs/reference/directory-structure_CN.md#consul-类型化构造)。 -其他四类旧插件保留原有 19 个头的全部字节。映射检查不代表 Consul、Kafka、OSS SDK 的运行时集成已经验证。Physic 保留未受支持的 PhysX 3 配置失败;参见[可选旧集成依赖](../../docs/dependencies_CN.md)。 +Kafka 与 Physic 保留原有公开头。映射检查不代表 Consul、Kafka、OSS SDK 的运行时集成已经验证。Physic 保留未受支持的 PhysX 3 配置失败;参见[可选旧集成依赖](../../docs/dependencies_CN.md)。 + +OSS SDK 使用类型化构造、私有工厂与显式入口,保留全部三个公开头和类型。公开布局发生变化,插件与消费者必须一起重建。插件默认保持关闭,实现仍为 TODO 骨架;构造/导出检查不增加对象存储客户端能力。参见 [OSS SDK 构造迁移](../../docs/reference/directory-structure_CN.md#oss-sdk-类型化构造)。 diff --git a/src/plugins/osssdk/oss_sdk_factory.hpp b/src/plugins/osssdk/oss_sdk_factory.hpp new file mode 100644 index 000000000..c4c6fba0a --- /dev/null +++ b/src/plugins/osssdk/oss_sdk_factory.hpp @@ -0,0 +1,13 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#pragma once + +#include + +#include "ark/osssdk/oss_sdk_module.hpp" + +// Plugin-private construction; deliberately absent from public header mappings. +namespace ark::detail { + +std::unique_ptr make_oss_sdk_module(); + +} // namespace ark::detail diff --git a/src/plugins/osssdk/oss_sdk_module_impl.cpp b/src/plugins/osssdk/oss_sdk_module_impl.cpp index 061bfa3a8..c5619a994 100644 --- a/src/plugins/osssdk/oss_sdk_module_impl.cpp +++ b/src/plugins/osssdk/oss_sdk_module_impl.cpp @@ -19,9 +19,12 @@ */ #include "ark/osssdk/oss_sdk_module_impl.hpp" +#include "oss_sdk_factory.hpp" namespace ark { -//method realization +std::unique_ptr detail::make_oss_sdk_module() { + return std::make_unique(); +} } // namespace ark diff --git a/src/plugins/osssdk/oss_sdk_module_impl.hpp b/src/plugins/osssdk/oss_sdk_module_impl.hpp index fad7a70e5..734e3b0d3 100644 --- a/src/plugins/osssdk/oss_sdk_module_impl.hpp +++ b/src/plugins/osssdk/oss_sdk_module_impl.hpp @@ -20,14 +20,13 @@ #pragma once +#include "ark/module_base.hpp" #include "ark/osssdk/oss_sdk_module.hpp" -#include "ark/plugin_manager.hpp" namespace ark { -class oss_sdk_module_impl final : public oss_sdk_module +class oss_sdk_module_impl final : public module_base { - ARK_DECLARE_MODULE_FUNCTIONS public: // TODO diff --git a/src/plugins/osssdk/oss_sdk_plugin.cpp b/src/plugins/osssdk/oss_sdk_plugin.cpp index df8d665b8..d20d4321e 100644 --- a/src/plugins/osssdk/oss_sdk_plugin.cpp +++ b/src/plugins/osssdk/oss_sdk_plugin.cpp @@ -18,21 +18,34 @@ * */ -#include "ark/osssdk/oss_sdk_module_impl.hpp" +#include + #include "ark/osssdk/oss_sdk_plugin.hpp" +#include "oss_sdk_factory.hpp" namespace ark { -ARK_DECLARE_PLUGIN_DLL_FUNCTION(oss_sdk_plugin) +ARK_EXPORT_FUNC void DllEntryPlugin(plugin_manager* manager, const std::string& name, logger* log) { + logger::init(log); + manager->register_plugin(name); +} + +ARK_EXPORT_FUNC void DllExitPlugin(plugin_manager* manager) { + manager->unregister_plugin(); +} void oss_sdk_plugin::install() { - // install module - ARK_REGISTER_MODULE(oss_sdk_module, oss_sdk_module_impl); + if (!register_module(detail::make_oss_sdk_module(), module_update::disabled)) + { + throw std::runtime_error("OSS SDK module registration rejected"); + } } void oss_sdk_plugin::uninstall() { - // uninstall module - ARK_DEREGISTER_MODULE(oss_sdk_module, oss_sdk_module_impl); + if (!unregister_module()) + { + throw std::runtime_error("OSS SDK module unregistration rejected"); + } } } // namespace ark diff --git a/src/plugins/osssdk/oss_sdk_plugin.hpp b/src/plugins/osssdk/oss_sdk_plugin.hpp index 500bf3167..349ad0c5a 100644 --- a/src/plugins/osssdk/oss_sdk_plugin.hpp +++ b/src/plugins/osssdk/oss_sdk_plugin.hpp @@ -20,11 +20,15 @@ #pragma once -#include "ark/plugin.hpp" -#include "ark/plugin_manager.hpp" +#include "ark/plugin_base.hpp" namespace ark { -ARK_DECLARE_PLUGIN(oss_sdk_plugin) +class oss_sdk_plugin final : public plugin_base +{ +public: + void install() override; + void uninstall() override; +}; } // namespace ark \ No newline at end of file diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 927446e32..3315fdc4d 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -224,6 +224,8 @@ if(ARK_BUILD_PROTO) add_dependencies(check-core check-kernel-initialization) endif() +add_subdirectory(../osssdk "${CMAKE_CURRENT_BINARY_DIR}/osssdk") + if(ARK_BUILD_PROTO AND ARK_BUILD_CONFIG) add_subdirectory(../consul "${CMAKE_CURRENT_BINARY_DIR}/consul") endif() diff --git a/tests/osssdk/CMakeLists.txt b/tests/osssdk/CMakeLists.txt new file mode 100644 index 000000000..cbf91e03f --- /dev/null +++ b/tests/osssdk/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +set(oss_sdk_source "${PROJECT_SOURCE_DIR}/src/plugins/osssdk") + +# Compile real assembly/factory locally too: private C++ symbols are not DLL exports. +add_executable(ark_oss_sdk_construction_tests ../core/main.cpp construction.cpp + "${oss_sdk_source}/oss_sdk_plugin.cpp" "${oss_sdk_source}/oss_sdk_module_impl.cpp") +set_property(TARGET ark_oss_sdk_construction_tests PROPERTY LINK_LIBRARIES + ark::osssdk) +target_include_directories(ark_oss_sdk_construction_tests PRIVATE "${oss_sdk_source}") + +# A controlled module exercises the actual entry/exit functions without object-storage services. +add_library(ark_oss_sdk_entry_probe SHARED entry_factory.cpp "${oss_sdk_source}/oss_sdk_plugin.cpp") +set_property(TARGET ark_oss_sdk_entry_probe PROPERTY LINK_LIBRARIES ark::osssdk) +target_include_directories(ark_oss_sdk_entry_probe PRIVATE "${oss_sdk_source}") +set_target_properties(ark_oss_sdk_entry_probe PROPERTIES PREFIX "") +add_executable(ark_oss_sdk_entry_tests entry.cpp) +set_property(TARGET ark_oss_sdk_entry_tests PROPERTY LINK_LIBRARIES ark::osssdk) + +add_custom_target(check-oss-sdk-construction + COMMAND $ + COMMAND $ "$/" "ark_oss_sdk_entry_probe" + DEPENDS ark_oss_sdk_construction_tests ark_oss_sdk_entry_tests ark_oss_sdk_entry_probe + USES_TERMINAL VERBATIM) + +if(TARGET ark_oss_sdk_plugin) + # Keep real DSO export inspection separate from the host linking static AFProto. + add_executable(ark_oss_sdk_exports_tests exports.cpp) + set_property(TARGET ark_oss_sdk_exports_tests PROPERTY LINK_LIBRARIES ark::core) + add_custom_target(check-oss-sdk-plugin + COMMAND $ "$/" "ark_oss_sdk_plugin" + DEPENDS ark_oss_sdk_exports_tests ark_oss_sdk_plugin check-oss-sdk-construction + USES_TERMINAL VERBATIM) + add_dependencies(check-core check-oss-sdk-plugin) +else() + add_dependencies(check-core check-oss-sdk-construction) +endif() diff --git a/tests/osssdk/construction.cpp b/tests/osssdk/construction.cpp new file mode 100644 index 000000000..66f268649 --- /dev/null +++ b/tests/osssdk/construction.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include "../network/doctest.h" + +#include +#include +#include "oss_sdk_factory.hpp" + +#if __has_include() +#error "OSS SDK public mappings must exclude the private factory" +#endif + +static_assert(std::is_base_of_v, ark::oss_sdk_module_impl>); +static_assert(std::is_base_of_v, ark::oss_sdk_plugin>); +static_assert(std::is_same_v); +static_assert(std::is_same_v>); + +TEST_CASE("OSS SDK real factory retains its public implementation and unpublished metadata") { + auto candidate = ark::detail::make_oss_sdk_module(); + REQUIRE(candidate != nullptr); + const auto* instance = candidate.get(); + CHECK(typeid(*instance) == typeid(ark::oss_sdk_module_impl)); + CHECK(candidate->get_plugin_manager() == nullptr); + CHECK(candidate->get_name().empty()); +} + +TEST_CASE("OSS SDK assembly retains the interface key, disabled updates and explicit ownership") { + ark::plugin_manager manager; + ark::oss_sdk_plugin owner; + owner.set_plugin_manager(&manager); + CHECK(owner.get_plugin_version() == 0); + CHECK(owner.get_plugin_name() == typeid(ark::oss_sdk_plugin).name()); + owner.set_plugin_conf("oss-sdk-construction.toml"); + CHECK(ark::oss_sdk_plugin::get_plugin_conf() == "oss-sdk-construction.toml"); + owner.install(); + auto* instance = manager.find_module(); + REQUIRE(instance != nullptr); + CHECK(typeid(*instance) == typeid(ark::oss_sdk_module_impl)); + CHECK(instance->get_plugin_manager() == &manager); + CHECK(instance->get_name() == typeid(ark::oss_sdk_module).name()); + // Insertion succeeds only when assembly left the module out of the update set. + CHECK(manager.add_update_module(instance)); + manager.remove_update_module(instance->get_name()); + CHECK_THROWS_AS(owner.install(), std::runtime_error); + CHECK(manager.find_module() == instance); + owner.uninstall(); + // Reinstallation proves the public registration key and ownership were released. + CHECK_NOTHROW(owner.install()); + CHECK_NOTHROW(owner.uninstall()); + CHECK(manager.update(1)); + CHECK_THROWS_AS(owner.uninstall(), std::runtime_error); +} diff --git a/tests/osssdk/entry.cpp b/tests/osssdk/entry.cpp new file mode 100644 index 000000000..d32165f9a --- /dev/null +++ b/tests/osssdk/entry.cpp @@ -0,0 +1,45 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include +#include +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected OSS SDK entry-probe directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load OSS SDK entry-probe library"); + auto events = reinterpret_cast(library.get_symbol("oss_sdk_entry_events")); + require(events && events(0) == 0 && events(1) == 0, "Entry-probe state is not fresh"); + require(library.get_symbol("DllEntryPlugin") && library.get_symbol("DllExitPlugin"), + "OSS SDK explicit entry/exit exports are missing"); + ark::plugin_manager manager; + require(manager.start(0, argv[1], "conf/", {{argv[2], "entry-probe.toml"}}), "OSS SDK explicit entry failed"); + auto* instance = manager.find_module(); + require(instance && instance->get_plugin_manager() == &manager && + instance->get_name() == typeid(ark::oss_sdk_module).name(), + "OSS SDK explicit entry lost interface registration metadata"); + require(events(0) == 1 && events(1) == 0, "OSS SDK entry did not transfer factory ownership"); + require(manager.update(1) && events(2) == 0, "OSS SDK entry changed disabled update selection"); + require(manager.stop(2) && events(1) == 1, "OSS SDK explicit exit did not destroy its module exactly once"); + require(manager.stop(3) && events(1) == 1, "Repeated stop invoked OSS SDK exit twice"); + require(library.unload(), "Cannot release OSS SDK observation handle"); + std::cout << "OSS SDK real assembly entry/exit, configuration, interface metadata and ownership passed\n"; +} diff --git a/tests/osssdk/entry_factory.cpp b/tests/osssdk/entry_factory.cpp new file mode 100644 index 000000000..b51dad2e7 --- /dev/null +++ b/tests/osssdk/entry_factory.cpp @@ -0,0 +1,55 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include + +#include +#include +#include "oss_sdk_factory.hpp" + +namespace { + +unsigned created{}; +unsigned destroyed{}; +unsigned updated{}; + +class entry_oss_sdk final : public ark::module_base +{ +public: + entry_oss_sdk() { + ++created; + } + + ~entry_oss_sdk() override { + ++destroyed; + } + + bool init() override { + return ark::oss_sdk_plugin::get_plugin_conf() == "conf/entry-probe.toml"; + } + + bool update() override { + ++updated; + return true; + } +}; + +} // namespace + +namespace ark::detail { + +std::unique_ptr make_oss_sdk_module() { + return std::make_unique(); +} + +} // namespace ark::detail + +ARK_EXPORT_FUNC unsigned oss_sdk_entry_events(unsigned event) { + switch (event) + { + case 0: + return created; + case 1: + return destroyed; + default: + return updated; + } +} diff --git a/tests/osssdk/exports.cpp b/tests/osssdk/exports.cpp new file mode 100644 index 000000000..118770b0b --- /dev/null +++ b/tests/osssdk/exports.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2026 OpenArkStudio. Licensed under Apache-2.0. See LICENSE. +#include +#include +#include + +#include + +namespace { + +void require(bool condition, const char* message) { + if (!condition) + { + std::cerr << message << std::endl; + std::_Exit(1); + } +} + +} // namespace + +int main(int argc, char* argv[]) { + require(argc == 3, "Expected plugin directory and library stem"); + ark::logger log; + ark::logger::init(&log); + log.set_log_func([](spdlog::level::level_enum, const std::string&) {}); + ark::dynamic_library library(argv[2]); + require(library.load(argv[1]), "Cannot load actual OSS SDK shared library"); + require(library.get_symbol("DllEntryPlugin") != nullptr, "Missing explicit DllEntryPlugin export"); + require(library.get_symbol("DllExitPlugin") != nullptr, "Missing explicit DllExitPlugin export"); + require(library.unload(), "Cannot unload OSS SDK shared library"); + std::cout << argv[2] << ": actual DSO load, explicit entry/exit exports and unload passed\n"; +}