From fd3d5f7e2a9d12102c6009bf0073cb7054e314cf Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 26 Aug 2026 14:27:13 +0000 Subject: [PATCH 1/2] Create GlobalHookManager. Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 11 +++ openfeature/global_hook_manager.cpp | 45 +++++++++ openfeature/global_hook_manager.h | 42 +++++++++ openfeature/openfeature_api.cpp | 19 +--- openfeature/openfeature_api.h | 3 +- test/BUILD | 11 +++ test/global_hook_manager_test.cpp | 136 ++++++++++++++++++++++++++++ 7 files changed, 251 insertions(+), 16 deletions(-) create mode 100644 openfeature/global_hook_manager.cpp create mode 100644 openfeature/global_hook_manager.h create mode 100644 test/global_hook_manager_test.cpp diff --git a/openfeature/BUILD b/openfeature/BUILD index 8c2653c..544c136 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -164,6 +164,16 @@ cc_library( ], ) +cc_library( + name = "global_hook_manager", + srcs = ["global_hook_manager.cpp"], + hdrs = ["global_hook_manager.h"], + include_prefix = "openfeature", + deps = [ + ":general_hook", + ], +) + cc_library( name = "hook_context", srcs = ["hook_context.cpp"], @@ -227,6 +237,7 @@ cc_library( ":client_api", ":evaluation_context", ":global_context_manager", + ":global_hook_manager", ":metadata", ":openfeature", ":provider", diff --git a/openfeature/global_hook_manager.cpp b/openfeature/global_hook_manager.cpp new file mode 100644 index 0000000..ffe6afe --- /dev/null +++ b/openfeature/global_hook_manager.cpp @@ -0,0 +1,45 @@ +#include "openfeature/global_hook_manager.h" + +#include +#include +#include +#include +#include + +#include "openfeature/general_hook.h" + +namespace openfeature { + +GlobalHookManager& GlobalHookManager::GetInstance() { + static GlobalHookManager instance; + return instance; +} + +void GlobalHookManager::AddHooks( + std::vector> hooks) { + std::unique_lock lock(hooks_mutex_); + hooks_.reserve(hooks_.size() + hooks.size()); + for (auto& hook : hooks) { + if (hook != nullptr) { + hooks_.push_back(std::move(hook)); + } + } +} + +void GlobalHookManager::AddHook(std::shared_ptr hook) { + if (hook == nullptr) return; + std::unique_lock lock(hooks_mutex_); + hooks_.push_back(std::move(hook)); +} + +std::vector> GlobalHookManager::GetHooks() const { + std::shared_lock lock(hooks_mutex_); + return hooks_; +} + +void GlobalHookManager::ClearHooks() { + std::unique_lock lock(hooks_mutex_); + hooks_.clear(); +} + +} // namespace openfeature diff --git a/openfeature/global_hook_manager.h b/openfeature/global_hook_manager.h new file mode 100644 index 0000000..0365fde --- /dev/null +++ b/openfeature/global_hook_manager.h @@ -0,0 +1,42 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_HOOK_MANAGER_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_HOOK_MANAGER_H_ + +#include +#include +#include + +#include "openfeature/general_hook.h" + +namespace openfeature { + +// Manages global hooks for the OpenFeature SDK. +// Provides thread-safe hook storage and access across the application. +class GlobalHookManager { + public: + static GlobalHookManager& GetInstance(); + + GlobalHookManager(const GlobalHookManager&) = delete; + GlobalHookManager& operator=(const GlobalHookManager&) = delete; + + // Adds one or more global hooks, appending them to existing hooks. + // Filters out nullptr entries. + void AddHooks(std::vector> hooks); + + // Adds a single global hook. Filters out nullptr entries. + void AddHook(std::shared_ptr hook); + + // Retrieves all configured global hooks. + std::vector> GetHooks() const; + + // Clears all configured global hooks. + void ClearHooks(); + + private: + GlobalHookManager() = default; + mutable std::shared_mutex hooks_mutex_; + std::vector> hooks_; +}; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_HOOK_MANAGER_H_ diff --git a/openfeature/openfeature_api.cpp b/openfeature/openfeature_api.cpp index e407161..856f4e6 100644 --- a/openfeature/openfeature_api.cpp +++ b/openfeature/openfeature_api.cpp @@ -2,6 +2,7 @@ #include "openfeature/client_api.h" #include "openfeature/global_context_manager.h" +#include "openfeature/global_hook_manager.h" namespace openfeature { @@ -86,30 +87,20 @@ ProviderStatus OpenFeatureAPI::GetProviderStatus( } void OpenFeatureAPI::AddHooks(std::vector> hooks) { - std::unique_lock lock(hooks_mutex_); - hooks_.reserve(hooks_.size() + hooks.size()); - for (auto& hook : hooks) { - if (hook != nullptr) { - hooks_.push_back(std::move(hook)); - } - } + GlobalHookManager::GetInstance().AddHooks(std::move(hooks)); } void OpenFeatureAPI::AddHook(std::shared_ptr hook) { - if (hook == nullptr) return; - std::unique_lock lock(hooks_mutex_); - hooks_.push_back(std::move(hook)); + GlobalHookManager::GetInstance().AddHook(std::move(hook)); } std::vector> OpenFeatureAPI::GetHooks() const { - std::shared_lock lock(hooks_mutex_); - return hooks_; + return GlobalHookManager::GetInstance().GetHooks(); } void OpenFeatureAPI::Shutdown() { provider_repository_.Shutdown(); - std::unique_lock lock(hooks_mutex_); - hooks_.clear(); + GlobalHookManager::GetInstance().ClearHooks(); } } // namespace openfeature \ No newline at end of file diff --git a/openfeature/openfeature_api.h b/openfeature/openfeature_api.h index d2a8fc6..ba96aec 100644 --- a/openfeature/openfeature_api.h +++ b/openfeature/openfeature_api.h @@ -10,6 +10,7 @@ #include "openfeature/evaluation_context.h" #include "openfeature/general_hook.h" #include "openfeature/global_context_manager.h" +#include "openfeature/global_hook_manager.h" #include "openfeature/metadata.h" #include "openfeature/openfeature.h" #include "openfeature/provider.h" @@ -85,8 +86,6 @@ class OpenFeatureAPI : public OpenFeature { private: ProviderRepository provider_repository_; - mutable std::shared_mutex hooks_mutex_; - std::vector> hooks_; OpenFeatureAPI(); }; diff --git a/test/BUILD b/test/BUILD index 70ba6f5..b1fcb67 100644 --- a/test/BUILD +++ b/test/BUILD @@ -103,6 +103,17 @@ cc_test( ], ) +cc_test( + name = "global_hook_manager_test", + srcs = ["global_hook_manager_test.cpp"], + deps = [ + "//openfeature:general_hook", + "//openfeature:global_hook_manager", + "//openfeature:hook", + "@googletest//:gtest_main", + ], +) + cc_test( name = "value_test", srcs = ["value_test.cpp"], diff --git a/test/global_hook_manager_test.cpp b/test/global_hook_manager_test.cpp new file mode 100644 index 0000000..1375b27 --- /dev/null +++ b/test/global_hook_manager_test.cpp @@ -0,0 +1,136 @@ +#include "openfeature/global_hook_manager.h" + +#include + +#include +#include +#include +#include + +#include "openfeature/general_hook.h" +#include "openfeature/hook.h" + +namespace openfeature { +namespace { + +class DummyTestHook1 : public BoolHook {}; +class DummyTestHook2 : public StringHook {}; +class DummyTestHook3 : public IntHook {}; + +class GlobalHookManagerTest : public ::testing::Test { + protected: + void SetUp() override { + GlobalHookManager::GetInstance().ClearHooks(); + } + + void TearDown() override { + GlobalHookManager::GetInstance().ClearHooks(); + } +}; + +TEST_F(GlobalHookManagerTest, ReturnsSameSingletonInstance) { + GlobalHookManager& instance1 = GlobalHookManager::GetInstance(); + GlobalHookManager& instance2 = GlobalHookManager::GetInstance(); + + EXPECT_EQ(&instance1, &instance2); +} + +TEST_F(GlobalHookManagerTest, InitialStateHasEmptyHooks) { + EXPECT_TRUE(GlobalHookManager::GetInstance().GetHooks().empty()); +} + +TEST_F(GlobalHookManagerTest, AddHookAppendsSingleHook) { + auto& manager = GlobalHookManager::GetInstance(); + std::shared_ptr hook1 = std::make_shared(); + + manager.AddHook(hook1); + + auto hooks = manager.GetHooks(); + ASSERT_EQ(hooks.size(), 1); + EXPECT_EQ(hooks[0], hook1); +} + +TEST_F(GlobalHookManagerTest, AddHooksAppendsMultipleHooksAndPreservesOrder) { + auto& manager = GlobalHookManager::GetInstance(); + std::shared_ptr hook1 = std::make_shared(); + std::shared_ptr hook2 = std::make_shared(); + + manager.AddHooks({hook1, hook2}); + + auto hooks = manager.GetHooks(); + ASSERT_EQ(hooks.size(), 2); + EXPECT_EQ(hooks[0], hook1); + EXPECT_EQ(hooks[1], hook2); + + std::shared_ptr hook3 = std::make_shared(); + manager.AddHook(hook3); + + hooks = manager.GetHooks(); + ASSERT_EQ(hooks.size(), 3); + EXPECT_EQ(hooks[0], hook1); + EXPECT_EQ(hooks[1], hook2); + EXPECT_EQ(hooks[2], hook3); +} + +TEST_F(GlobalHookManagerTest, AddHookAndAddHooksFiltersNullptrs) { + auto& manager = GlobalHookManager::GetInstance(); + manager.AddHook(nullptr); + EXPECT_TRUE(manager.GetHooks().empty()); + + std::shared_ptr valid_hook = std::make_shared(); + manager.AddHooks({nullptr, valid_hook, nullptr}); + + auto hooks = manager.GetHooks(); + ASSERT_EQ(hooks.size(), 1); + EXPECT_EQ(hooks[0], valid_hook); +} + +TEST_F(GlobalHookManagerTest, ClearHooksRemovesAllHooks) { + auto& manager = GlobalHookManager::GetInstance(); + std::shared_ptr hook1 = std::make_shared(); + std::shared_ptr hook2 = std::make_shared(); + manager.AddHooks({hook1, hook2}); + + ASSERT_EQ(manager.GetHooks().size(), 2); + + manager.ClearHooks(); + EXPECT_TRUE(manager.GetHooks().empty()); +} + +TEST_F(GlobalHookManagerTest, ThreadSafetyStressTest) { + auto& manager = GlobalHookManager::GetInstance(); + std::atomic stop{false}; + + // Writer thread adding hooks + std::thread writer([&]() { + while (!stop) { + manager.AddHook(std::make_shared()); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }); + + // Reader threads reading hooks + std::vector readers; + for (int i = 0; i < 8; ++i) { + readers.emplace_back([&]() { + while (!stop) { + auto hooks = manager.GetHooks(); + volatile size_t s = hooks.size(); + (void)s; + } + }); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + stop = true; + writer.join(); + for (auto& t : readers) { + t.join(); + } + + EXPECT_FALSE(manager.GetHooks().empty()); +} + +} // namespace +} // namespace openfeature From aa011c9d1690080f965ff86b5c56cd20ce621b01 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 26 Aug 2026 14:54:44 +0000 Subject: [PATCH 2/2] fix linter Signed-off-by: NeaguGeorgiana23 --- test/global_hook_manager_test.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/test/global_hook_manager_test.cpp b/test/global_hook_manager_test.cpp index 1375b27..ae674ec 100644 --- a/test/global_hook_manager_test.cpp +++ b/test/global_hook_manager_test.cpp @@ -19,13 +19,9 @@ class DummyTestHook3 : public IntHook {}; class GlobalHookManagerTest : public ::testing::Test { protected: - void SetUp() override { - GlobalHookManager::GetInstance().ClearHooks(); - } + void SetUp() override { GlobalHookManager::GetInstance().ClearHooks(); } - void TearDown() override { - GlobalHookManager::GetInstance().ClearHooks(); - } + void TearDown() override { GlobalHookManager::GetInstance().ClearHooks(); } }; TEST_F(GlobalHookManagerTest, ReturnsSameSingletonInstance) { @@ -97,6 +93,10 @@ TEST_F(GlobalHookManagerTest, ClearHooksRemovesAllHooks) { EXPECT_TRUE(manager.GetHooks().empty()); } +constexpr int kWriterSleepIntervalMs = 1; +constexpr int kReaderThreadCount = 8; +constexpr int kStressTestDurationMs = 100; + TEST_F(GlobalHookManagerTest, ThreadSafetyStressTest) { auto& manager = GlobalHookManager::GetInstance(); std::atomic stop{false}; @@ -105,28 +105,30 @@ TEST_F(GlobalHookManagerTest, ThreadSafetyStressTest) { std::thread writer([&]() { while (!stop) { manager.AddHook(std::make_shared()); - std::this_thread::sleep_for(std::chrono::milliseconds(1)); + std::this_thread::sleep_for( + std::chrono::milliseconds(kWriterSleepIntervalMs)); } }); // Reader threads reading hooks std::vector readers; - for (int i = 0; i < 8; ++i) { + readers.reserve(kReaderThreadCount); + for (int i = 0; i < kReaderThreadCount; ++i) { readers.emplace_back([&]() { while (!stop) { auto hooks = manager.GetHooks(); - volatile size_t s = hooks.size(); - (void)s; + volatile size_t hook_count = hooks.size(); + (void)hook_count; } }); } - std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(kStressTestDurationMs)); stop = true; writer.join(); - for (auto& t : readers) { - t.join(); + for (auto& reader_thread : readers) { + reader_thread.join(); } EXPECT_FALSE(manager.GetHooks().empty());