diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..e0741a8 --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +8.5.1 \ No newline at end of file diff --git a/openfeature/BUILD b/openfeature/BUILD index f05a514..fa0e587 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -1,3 +1,5 @@ +load("@rules_cc//cc:defs.bzl", "cc_library") + package( default_visibility = ["//visibility:public"], ) @@ -55,6 +57,16 @@ cc_library( include_prefix = "openfeature", ) +cc_library( + name = "global_context_manager", + srcs = ["global_context_manager.cpp"], + hdrs = ["global_context_manager.h"], + include_prefix = "openfeature", + deps = [ + ":evaluation_context", + ], +) + cc_library( name = "metadata", hdrs = ["metadata.h"], diff --git a/openfeature/global_context_manager.cpp b/openfeature/global_context_manager.cpp new file mode 100644 index 0000000..c356e22 --- /dev/null +++ b/openfeature/global_context_manager.cpp @@ -0,0 +1,23 @@ +#include "openfeature/global_context_manager.h" + +#include + +namespace openfeature { + +GlobalContextManager& GlobalContextManager::GetInstance() { + static GlobalContextManager instance; + return instance; +} + +void GlobalContextManager::SetGlobalEvaluationContext( + const EvaluationContext& ctx) { + std::unique_lock lock(context_mutex_); + global_evaluation_context_ = ctx; +} + +EvaluationContext GlobalContextManager::GetGlobalEvaluationContext() const { + std::shared_lock lock(context_mutex_); + return global_evaluation_context_; +} + +} // namespace openfeature \ No newline at end of file diff --git a/openfeature/global_context_manager.h b/openfeature/global_context_manager.h new file mode 100644 index 0000000..53a24f2 --- /dev/null +++ b/openfeature/global_context_manager.h @@ -0,0 +1,34 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_CONTEXT_MANAGER_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_CONTEXT_MANAGER_H_ + +#include +#include + +#include "openfeature/evaluation_context.h" + +namespace openfeature { + +// Manages the global Evaluation Context for the OpenFeature SDK. +// This data is static across the application (unless explicitly changed) +class GlobalContextManager { + public: + static GlobalContextManager& GetInstance(); + + GlobalContextManager(const GlobalContextManager&) = delete; + GlobalContextManager& operator=(const GlobalContextManager&) = delete; + + // Updates the global evaluation context. + void SetGlobalEvaluationContext(const EvaluationContext& ctx); + + // Retrieves the current global evaluation context. + EvaluationContext GetGlobalEvaluationContext() const; + + private: + GlobalContextManager() = default; + EvaluationContext global_evaluation_context_; + mutable std::shared_mutex context_mutex_; +}; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_GLOBAL_CONTEXT_MANAGER_H_ \ No newline at end of file diff --git a/test/BUILD b/test/BUILD index bddb116..3d38a78 100644 --- a/test/BUILD +++ b/test/BUILD @@ -1,3 +1,5 @@ +load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test") + cc_library( name = "mock_feature_provider", testonly = True, @@ -46,3 +48,12 @@ cc_test( "@googletest//:gtest_main", ], ) + +cc_test( + name = "global_context_manager_test", + srcs = ["global_context_manager_test.cpp"], + deps = [ + "//openfeature:global_context_manager", + "@googletest//:gtest_main", + ], +) diff --git a/test/global_context_manager_test.cpp b/test/global_context_manager_test.cpp new file mode 100644 index 0000000..9986f4e --- /dev/null +++ b/test/global_context_manager_test.cpp @@ -0,0 +1,89 @@ +#include "openfeature/global_context_manager.h" + +#include + +#include +#include +#include + +#include "openfeature/evaluation_context.h" + +using namespace openfeature; + +class GlobalContextManagerTest : public ::testing::Test { + protected: + // Reset to a clean state before every test. + void SetUp() override { + GlobalContextManager::GetInstance().SetGlobalEvaluationContext( + EvaluationContext{}); + } +}; + +TEST_F(GlobalContextManagerTest, ReturnsSameInstance) { + GlobalContextManager& instance1 = GlobalContextManager::GetInstance(); + GlobalContextManager& instance2 = GlobalContextManager::GetInstance(); + + EXPECT_EQ(&instance1, &instance2); +} + +TEST_F(GlobalContextManagerTest, SetAndGetContext) { + GlobalContextManager& manager = GlobalContextManager::GetInstance(); + EvaluationContext input_ctx; + + EXPECT_NO_THROW(manager.SetGlobalEvaluationContext(input_ctx)); + + EvaluationContext output_ctx = manager.GetGlobalEvaluationContext(); + EXPECT_NO_THROW(output_ctx = manager.GetGlobalEvaluationContext()); + + // NOTE: Since EvaluationContext is currently an empty class, + // we cannot assert EQ or check member values. + // Currently, the test simply proves the API calls succeed and copy semantics + // work. + + // TODO: Add assertions. +} + +TEST_F(GlobalContextManagerTest, ThreadSafetyStressTest) { + GlobalContextManager& manager = GlobalContextManager::GetInstance(); + std::atomic stop{false}; + + // Writer Thread: Continuously updates the context. + std::thread writer([&]() { + while (!stop) { + EvaluationContext ctx; + // In a real scenario, we would populate ctx with different data here. + manager.SetGlobalEvaluationContext(ctx); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + }); + + // Reader Threads: Continuously read the context. + std::vector readers; + for (int i = 0; i < 10; ++i) { + readers.emplace_back([&]() { + while (!stop) { + // Just calling the getter to ensure locks work and no race conditions + // occur resulting in a crash (segfault). + EvaluationContext ctx = manager.GetGlobalEvaluationContext(); + + // Prevent optimization from removing the call. + volatile size_t size = sizeof(ctx); + (void)size; + } + }); + } + + // Let the chaos run for a short duration. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + stop = true; + writer.join(); + for (auto& t : readers) { + t.join(); + } +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file