From 5c5309f730cf0fa26b72c8da8a7b07fd7b99d575 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 10 Dec 2025 15:40:26 +0000 Subject: [PATCH 1/5] Add Provider Repository Signed-off-by: NeaguGeorgiana23 --- openfeature/BUILD | 14 ++ openfeature/provider_repository.cpp | 198 ++++++++++++++++++ openfeature/provider_repository.h | 83 ++++++++ test/BUILD | 10 + test/provider_repository_test.cpp | 314 ++++++++++++++++++++++++++++ 5 files changed, 619 insertions(+) create mode 100644 openfeature/provider_repository.cpp create mode 100644 openfeature/provider_repository.h create mode 100644 test/provider_repository_test.cpp diff --git a/openfeature/BUILD b/openfeature/BUILD index ac0f8e4..e32039a 100644 --- a/openfeature/BUILD +++ b/openfeature/BUILD @@ -77,6 +77,20 @@ cc_library( ], ) +cc_library( + name = "provider_repository", + srcs = ["provider_repository.cpp"], + hdrs = ["provider_repository.h"], + deps = [ + "@abseil-cpp//absl/status", + ":evaluation_context", + ":feature_provider_status_manager", + ":provider", + ":provider_status", + ":noop_provider", + ], +) + cc_library( name = "provider_status", hdrs = ["provider_status.h"], diff --git a/openfeature/provider_repository.cpp b/openfeature/provider_repository.cpp new file mode 100644 index 0000000..b71d069 --- /dev/null +++ b/openfeature/provider_repository.cpp @@ -0,0 +1,198 @@ +#include "openfeature/provider_repository.h" + +#include +#include +#include + +#include "openfeature/noop_provider.h" + +namespace openfeature { + +ProviderRepository::ProviderRepository() { + // Initialize with a NoopProvider by default, which is always ready. + auto noop_provider = std::make_shared(); + auto status_manager = FeatureProviderStatusManager::Create(noop_provider); + if (status_manager.ok()) { + std::unique_lock lock(repo_mutex_); + default_manager_ = std::move(status_manager.value()); + default_manager_->SetStatus(ProviderStatus::kReady); + } +} + +ProviderRepository::~ProviderRepository() { Shutdown(); } + +std::shared_ptr +ProviderRepository::GetFeatureProviderStatusManager(std::string_view domain) { + std::shared_lock lock(repo_mutex_); + + if (domain.empty()) { + return default_manager_; + } + + // Look up domain in map. + auto it = provider_manager_.find(std::string(domain)); + if (it != provider_manager_.end()) { + return it->second; + } + + // Fallback to default if no provider was found. + return default_manager_; +} + +std::shared_ptr ProviderRepository::GetProvider( + std::string_view domain) { + auto manager = GetFeatureProviderStatusManager(domain); + + if (manager) { + return manager->GetProvider(); + } + return nullptr; +} + +void ProviderRepository::SetProvider(std::shared_ptr provider, + EvaluationContext ctx, bool waitForInit) { + if (!provider) { + std::cerr << "Provider cannot be null" << std::endl; + return; + } + PrepareAndInitializeProvider(std::nullopt, std::move(provider), ctx, + waitForInit); +} + +void ProviderRepository::SetProvider(std::string_view domain, + std::shared_ptr provider, + EvaluationContext ctx, bool waitForInit) { + if (!provider) { + std::cerr << "Provider cannot be null" << std::endl; + return; + } + + if (domain.empty()) { + SetProvider(std::move(provider), ctx, waitForInit); + return; + } + + PrepareAndInitializeProvider(std::string(domain), std::move(provider), ctx, + waitForInit); +} + +ProviderStatus ProviderRepository::GetProviderStatus(std::string_view domain) { + return GetFeatureProviderStatusManager(domain)->GetStatus(); +} + +void ProviderRepository::Shutdown() { + std::unique_lock lock(repo_mutex_); + + if (default_manager_) { + default_manager_->Shutdown(); + default_manager_.reset(); + } + + for (auto& pair : provider_manager_) { + if (pair.second->GetStatus() != ProviderStatus::kNotReady) { + pair.second->Shutdown(); + } + } + provider_manager_.clear(); +} + +void ProviderRepository::PrepareAndInitializeProvider( + const std::optional domain, + std::shared_ptr new_provider, EvaluationContext ctx, + bool waitForInit) { + std::shared_ptr new_status_manager; + std::shared_ptr old_status_manager; + + { // Scoping for the unique_lock. + std::unique_lock lock(repo_mutex_); + + auto existing_manager = GetExistingStatusManagerForProvider(new_provider); + if (!existing_manager) { + auto manager = FeatureProviderStatusManager::Create(new_provider); + if (!manager.ok()) { + std::cerr << "Failed to create FeatureProviderStatusManager: " + << manager.status() << std::endl; + return; + } + new_status_manager = std::move(manager.value()); + } else { + new_status_manager = existing_manager; + } + + if (domain) { + // Setting a named provider. + auto it = provider_manager_.find(domain.value()); + if (it != provider_manager_.end()) { + old_status_manager = it->second; + } + provider_manager_[domain.value()] = new_status_manager; + } else { + // Setting the default provider. + old_status_manager = default_manager_; + default_manager_ = new_status_manager; + } + } // Release the lock before running initialization logic. + + // Decide whether to initialize sync or async. + if (waitForInit) { + InitializeProvider(new_status_manager, old_status_manager, ctx); + } else { + std::thread([this, new_status_manager, old_status_manager, ctx] { + InitializeProvider(new_status_manager, old_status_manager, ctx); + }).detach(); + } +} + +void ProviderRepository::InitializeProvider( + std::shared_ptr new_status_manager, + std::shared_ptr old_status_manager, + EvaluationContext ctx) { + if (new_status_manager->GetStatus() == ProviderStatus::kNotReady) { + new_status_manager->Init(ctx); + } + + if (new_status_manager->GetStatus() == ProviderStatus::kReady) { + ShutdownOldProvider(old_status_manager); + } +} + +void ProviderRepository::ShutdownOldProvider( + std::shared_ptr old_status_manager) { + if (old_status_manager) { + std::shared_lock lock(repo_mutex_); + + if (!IsStatusManagerRegistered(old_status_manager)) { + old_status_manager->Shutdown(); + } + } +} + +std::shared_ptr +ProviderRepository::GetExistingStatusManagerForProvider( + const std::shared_ptr& provider) { + if (default_manager_ && default_manager_->GetProvider() == provider) { + return default_manager_; + } + + for (const auto& pair : provider_manager_) { + if (pair.second && pair.second->GetProvider() == provider) { + return pair.second; + } + } + return nullptr; +} + +bool ProviderRepository::IsStatusManagerRegistered( + const std::shared_ptr& manager) { + if (default_manager_ == manager) { + return true; + } + for (const auto& pair : provider_manager_) { + if (pair.second == manager) { + return true; + } + } + return false; +} + +} // namespace openfeature diff --git a/openfeature/provider_repository.h b/openfeature/provider_repository.h new file mode 100644 index 0000000..1ea853f --- /dev/null +++ b/openfeature/provider_repository.h @@ -0,0 +1,83 @@ +#ifndef CPP_SDK_INCLUDE_OPENFEATURE_PROVIDER_REPOSITORY_H_ +#define CPP_SDK_INCLUDE_OPENFEATURE_PROVIDER_REPOSITORY_H_ + +#include +#include +#include +#include +#include +#include + +#include "absl/status/status.h" +#include "openfeature/evaluation_context.h" +#include "openfeature/feature_provider_status_manager.h" +#include "openfeature/provider.h" +#include "openfeature/provider_status.h" + +namespace openfeature { + +class ProviderRepository { + public: + ProviderRepository(); + ~ProviderRepository(); + + ProviderRepository(const ProviderRepository&) = delete; + ProviderRepository& operator=(const ProviderRepository&) = delete; + + // If the domain is empty, fetch status manager for the default provider. + // Otherwise, fetch the status manager for a specific provider. + std::shared_ptr GetFeatureProviderStatusManager( + std::string_view domain = ""); + + // If the domain is empty then GetProvider returns the default + // FeatureProvider. Otherwise it returns the FeatureProvider for the + // domain. + std::shared_ptr GetProvider(std::string_view domain = ""); + + // Set the default provider. + void SetProvider(std::shared_ptr provider, + EvaluationContext ctx, bool waitForInit); + + // Add a provider for a domain. + void SetProvider(std::string_view domain, + std::shared_ptr provider, + EvaluationContext ctx, bool waitForInit); + + // Fetch the status of a provider for a domain. + // If the domain is not set, return the default provider status. + // If not found, return the default. + ProviderStatus GetProviderStatus(std::string_view domain = ""); + + // Shuts down all registered providers and clears the repository. + void Shutdown(); + + private: + void PrepareAndInitializeProvider( + const std::optional domain, + std::shared_ptr new_provider, EvaluationContext ctx, + bool waitForInit); + + void InitializeProvider( + std::shared_ptr new_status_manager, + std::shared_ptr old_status_manager, + EvaluationContext ctx); + + void ShutdownOldProvider( + std::shared_ptr old_status_manager); + + std::shared_ptr + GetExistingStatusManagerForProvider( + const std::shared_ptr& provider); + + bool IsStatusManagerRegistered( + const std::shared_ptr& manager); + + std::unordered_map> + provider_manager_; + std::shared_ptr default_manager_; + mutable std::shared_mutex repo_mutex_; +}; + +} // namespace openfeature + +#endif // CPP_SDK_INCLUDE_OPENFEATURE_PROVIDER_REPOSITORY_H_ diff --git a/test/BUILD b/test/BUILD index 9f96a74..bddb116 100644 --- a/test/BUILD +++ b/test/BUILD @@ -36,3 +36,13 @@ cc_test( "@googletest//:gtest_main", ], ) + +cc_test( + name = "provider_repository_test", + srcs = ["provider_repository_test.cpp"], + deps = [ + ":mock_feature_provider", + "//openfeature:provider_repository", + "@googletest//:gtest_main", + ], +) diff --git a/test/provider_repository_test.cpp b/test/provider_repository_test.cpp new file mode 100644 index 0000000..3900e80 --- /dev/null +++ b/test/provider_repository_test.cpp @@ -0,0 +1,314 @@ +#include "openfeature/provider_repository.h" + +#include +#include + +#include +#include +#include + +#include "mocks/mock_feature_provider.h" +#include "openfeature/noop_provider.h" + +using namespace openfeature; +using ::testing::_; +using ::testing::InSequence; +using ::testing::Return; + +class ProviderRepositoryTest : public ::testing::Test { + protected: + ProviderRepository repo; + EvaluationContext ctx; +}; + +// Test to verify the constructor initializes with a NoopProvider. +TEST_F(ProviderRepositoryTest, ConstructorInitializesWithNoopProvider) { + std::shared_ptr provider = repo.GetProvider(); + ASSERT_NE(provider, nullptr); + EXPECT_NE(dynamic_cast(provider.get()), nullptr); + EXPECT_EQ(repo.GetProvider()->GetMetadata().name, "Noop Provider"); + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kReady); +} + +// Test to verify that GetFeatureProviderStatusManager returns the correct +// manager. +TEST_F(ProviderRepositoryTest, + GetFeatureProviderStatusManagerReturnsCorrectManager) { + // On initialization, it should return the default manager. + std::shared_ptr initial_default_manager = + repo.GetFeatureProviderStatusManager(); + ASSERT_NE(initial_default_manager, nullptr); + ASSERT_EQ(initial_default_manager->GetProvider()->GetMetadata().name, + "Noop Provider"); + + // Asking for a non-existent domain should return the default manager. + std::shared_ptr non_existent_manager = + repo.GetFeatureProviderStatusManager("non-existent"); + EXPECT_EQ(non_existent_manager, initial_default_manager); + + // After setting a named provider, it should return the new manager. + std::shared_ptr mock_provider = + std::make_shared(); + std::string domain = "my-domain"; + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + repo.SetProvider(domain, mock_provider, ctx, true); + + std::shared_ptr named_manager = + repo.GetFeatureProviderStatusManager(domain); + ASSERT_NE(named_manager, nullptr); + EXPECT_NE(named_manager, initial_default_manager); + EXPECT_EQ(named_manager->GetProvider(), mock_provider); + + // Getting the default manager should still return the original one. + std::shared_ptr current_default_manager = + repo.GetFeatureProviderStatusManager(); + EXPECT_EQ(current_default_manager, initial_default_manager); +} + +// Test to verify GetProvider returns the default when the domain is empty. +TEST_F(ProviderRepositoryTest, GetProviderReturnsDefaultWhenDomainIsEmpty) { + std::shared_ptr default_provider = repo.GetProvider(); + std::shared_ptr empty_domain_provider = repo.GetProvider(""); + EXPECT_EQ(default_provider, empty_domain_provider); +} + +// Test to verify GetProvider returns the default when the domain is not found. +TEST_F(ProviderRepositoryTest, GetProviderReturnsDefaultWhenDomainNotFound) { + std::shared_ptr default_provider = repo.GetProvider(); + std::shared_ptr not_found_provider = + repo.GetProvider("non-existent-domain"); + EXPECT_EQ(default_provider, not_found_provider); +} + +// Test to verify setting and retrieving a named provider. +TEST_F(ProviderRepositoryTest, SetAndGetNamedProvider) { + std::shared_ptr mock_provider = + std::make_shared(); + std::string domain = "my-domain"; + + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + + repo.SetProvider(domain, mock_provider, ctx, true); + + EXPECT_EQ(repo.GetProvider(domain), mock_provider); + EXPECT_NE(dynamic_cast(repo.GetProvider().get()), nullptr); +} + +// Test to verify that setting the default provider replaces the NoopProvider. +TEST_F(ProviderRepositoryTest, SetDefaultProviderReplacesNoop) { + std::shared_ptr mock_provider = + std::make_shared(); + + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + + repo.SetProvider(mock_provider, ctx, true); + + EXPECT_EQ(repo.GetProvider(), mock_provider); +} + +// Test that setting a null provider for the default does not change the state. +TEST_F(ProviderRepositoryTest, SetProviderWithNullProviderDoesNothing) { + std::shared_ptr initial_provider = repo.GetProvider(); + ProviderStatus initial_status = repo.GetProviderStatus(); + testing::internal::CaptureStderr(); + + repo.SetProvider(nullptr, ctx, true); + std::string output = testing::internal::GetCapturedStderr(); + + EXPECT_THAT(output, testing::HasSubstr("Provider cannot be null")); + EXPECT_EQ(repo.GetProvider(), initial_provider); + EXPECT_EQ(repo.GetProviderStatus(), initial_status); +} + +// Test that setting a null provider for a named domain does not change the +// state. +TEST_F(ProviderRepositoryTest, SetNamedProviderWithNullProviderDoesNothing) { + const std::string domain = "my-domain"; + std::shared_ptr initial_provider_for_domain = + repo.GetProvider(domain); + ProviderStatus initial_status = repo.GetProviderStatus(domain); + + testing::internal::CaptureStderr(); + + repo.SetProvider(domain, nullptr, ctx, true); + std::string output = testing::internal::GetCapturedStderr(); + + EXPECT_THAT(output, testing::HasSubstr("Provider cannot be null")); + EXPECT_EQ(repo.GetProvider(domain), initial_provider_for_domain); + EXPECT_EQ(repo.GetProviderStatus(domain), initial_status); +} + +// Test to verify that SetProvider waits for initialization to complete. +TEST_F(ProviderRepositoryTest, SetProviderWaitsForInitialization) { + std::shared_ptr mock_provider = + std::make_shared(); + + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + + repo.SetProvider(mock_provider, ctx, true); + + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kReady); +} + +// Test to verify that SetProvider initializes asynchronously. +TEST_F(ProviderRepositoryTest, SetProviderDoesNotWaitForInitialization) { + std::shared_ptr mock_provider = + std::make_shared(); + + std::promise init_can_complete; + std::future init_future = init_can_complete.get_future(); + + EXPECT_CALL(*mock_provider, Init(_)).WillOnce([&init_future](const auto&) { + init_future.get(); + return absl::OkStatus(); + }); + + repo.SetProvider(mock_provider, ctx, false); + + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kNotReady); + + init_can_complete.set_value(); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kReady); +} + +// Test to verify that a failed initialization sets the status to kError. +TEST_F(ProviderRepositoryTest, SetProviderWithFailedInitSetsErrorStatus) { + std::shared_ptr mock_provider = + std::make_shared(); + + EXPECT_CALL(*mock_provider, Init(_)) + .WillOnce(Return(absl::InternalError("Init failed"))); + + repo.SetProvider(mock_provider, ctx, true); + + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kError); +} + +// Test to verify the old provider is shutdown after a new one is ready. +TEST_F(ProviderRepositoryTest, OldProviderIsShutdownAfterNewOneIsReady) { + std::shared_ptr mock_provider1 = + std::make_shared(); + std::shared_ptr mock_provider2 = + std::make_shared(); + + EXPECT_CALL(*mock_provider1, Init(_)).WillOnce(Return(absl::OkStatus())); + repo.SetProvider(mock_provider1, ctx, true); + EXPECT_EQ(repo.GetProvider(), mock_provider1); + ASSERT_EQ(repo.GetProviderStatus(), ProviderStatus::kReady); + + { + InSequence seq; + EXPECT_CALL(*mock_provider2, Init(_)).WillOnce(Return(absl::OkStatus())); + EXPECT_CALL(*mock_provider1, Shutdown()).WillOnce(Return(absl::OkStatus())); + } + + repo.SetProvider(mock_provider2, ctx, true); + EXPECT_EQ(repo.GetProvider(), mock_provider2); + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kReady); +} + +// Test to verify the old provider is not shut down if the new one fails to +// init. +TEST_F(ProviderRepositoryTest, OldProviderIsNotShutdownIfNewOneFailsToInit) { + std::shared_ptr mock_provider1 = + std::make_shared(); + std::shared_ptr mock_provider2 = + std::make_shared(); + + EXPECT_CALL(*mock_provider1, Init(_)).WillOnce(Return(absl::OkStatus())); + repo.SetProvider(mock_provider1, ctx, true); + + EXPECT_CALL(*mock_provider2, Init(_)) + .WillOnce(Return(absl::InternalError("Init failed"))); + EXPECT_CALL(*mock_provider1, Shutdown()).Times(0); + + repo.SetProvider(mock_provider2, ctx, true); + + // The new provider should be set, but in an error state. + EXPECT_EQ(repo.GetProvider(), mock_provider2); + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kError); +} + +// Test to verify Shutdown calls Shutdown on all registered providers. +TEST_F(ProviderRepositoryTest, ShutdownAllProviders) { + std::shared_ptr mock_default = + std::make_shared(); + std::shared_ptr mock_named_1 = + std::make_shared(); + std::string domain_1 = "my-first-domain"; + std::shared_ptr mock_named_2 = + std::make_shared(); + std::string domain_2 = "my-second-domain"; + std::string domain_3 = "my-third-domain"; + + EXPECT_CALL(*mock_default, Init(_)).WillOnce(Return(absl::OkStatus())); + EXPECT_CALL(*mock_named_1, Init(_)).WillOnce(Return(absl::OkStatus())); + EXPECT_CALL(*mock_named_2, Init(_)).WillOnce(Return(absl::OkStatus())); + + repo.SetProvider(mock_default, ctx, true); + repo.SetProvider(domain_1, mock_named_1, ctx, true); + repo.SetProvider(domain_2, mock_named_2, ctx, true); + repo.SetProvider(domain_3, mock_named_2, ctx, true); + + EXPECT_CALL(*mock_default, Shutdown()).WillOnce(Return(absl::OkStatus())); + EXPECT_CALL(*mock_named_1, Shutdown()).WillOnce(Return(absl::OkStatus())); + EXPECT_CALL(*mock_named_2, Shutdown()).WillOnce(Return(absl::OkStatus())); + + repo.Shutdown(); + EXPECT_EQ(repo.GetFeatureProviderStatusManager(), nullptr); +} + +// Test that setting an existing provider in a new location reuses the manager. +TEST_F(ProviderRepositoryTest, SetExistingProviderReusesManager) { + std::shared_ptr mock_provider = + std::make_shared(); + std::string domain = "my-domain"; + + EXPECT_CALL(*mock_provider, Init(_)).WillOnce(Return(absl::OkStatus())); + + repo.SetProvider(mock_provider, ctx, true); + std::shared_ptr default_manager = + repo.GetFeatureProviderStatusManager(); + + repo.SetProvider(domain, mock_provider, ctx, true); + std::shared_ptr named_manager = + repo.GetFeatureProviderStatusManager(domain); + + EXPECT_EQ(default_manager, named_manager); +} + +// Test that replacing a provider does not shut it down if it's still in use. +TEST_F(ProviderRepositoryTest, ReplacingProviderDoesNotShutdownIfStillInUse) { + std::shared_ptr mock_provider1 = + std::make_shared(); + std::shared_ptr mock_provider2 = + std::make_shared(); + std::string domain_a = "domain-A"; + + EXPECT_CALL(*mock_provider1, Init(_)).WillOnce(Return(absl::OkStatus())); + EXPECT_CALL(*mock_provider2, Init(_)).WillOnce(Return(absl::OkStatus())); + + repo.SetProvider(mock_provider1, ctx, true); + repo.SetProvider(domain_a, mock_provider1, ctx, true); + + std::shared_ptr manager_for_provider1 = + repo.GetFeatureProviderStatusManager(); + ASSERT_EQ(manager_for_provider1->GetProvider(), mock_provider1); + + EXPECT_CALL(*mock_provider1, Shutdown()).Times(0); + repo.SetProvider(mock_provider2, ctx, true); + + EXPECT_EQ(manager_for_provider1->GetStatus(), ProviderStatus::kReady); + + EXPECT_CALL(*mock_provider1, Shutdown()).WillOnce(Return(absl::OkStatus())); + repo.SetProvider(domain_a, mock_provider2, ctx, true); + + EXPECT_EQ(manager_for_provider1->GetStatus(), ProviderStatus::kNotReady); +} + +int main(int argc, char** argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From bab6a76e5c598b1af8e99e88e44f0f96765cb4a0 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Wed, 10 Dec 2025 16:26:34 +0000 Subject: [PATCH 2/5] Fix comments Signed-off-by: NeaguGeorgiana23 --- openfeature/provider_repository.cpp | 16 +++++++++++----- openfeature/provider_repository.h | 10 +++++----- test/provider_repository_test.cpp | 11 +++++++++++ 3 files changed, 27 insertions(+), 10 deletions(-) diff --git a/openfeature/provider_repository.cpp b/openfeature/provider_repository.cpp index b71d069..40501be 100644 --- a/openfeature/provider_repository.cpp +++ b/openfeature/provider_repository.cpp @@ -50,7 +50,8 @@ std::shared_ptr ProviderRepository::GetProvider( } void ProviderRepository::SetProvider(std::shared_ptr provider, - EvaluationContext ctx, bool waitForInit) { + const EvaluationContext& ctx, + bool waitForInit) { if (!provider) { std::cerr << "Provider cannot be null" << std::endl; return; @@ -61,7 +62,8 @@ void ProviderRepository::SetProvider(std::shared_ptr provider, void ProviderRepository::SetProvider(std::string_view domain, std::shared_ptr provider, - EvaluationContext ctx, bool waitForInit) { + const EvaluationContext& ctx, + bool waitForInit) { if (!provider) { std::cerr << "Provider cannot be null" << std::endl; return; @@ -77,7 +79,11 @@ void ProviderRepository::SetProvider(std::string_view domain, } ProviderStatus ProviderRepository::GetProviderStatus(std::string_view domain) { - return GetFeatureProviderStatusManager(domain)->GetStatus(); + auto manager = GetFeatureProviderStatusManager(domain); + if (manager) { + return manager->GetStatus(); + } + return ProviderStatus::kNotReady; } void ProviderRepository::Shutdown() { @@ -98,7 +104,7 @@ void ProviderRepository::Shutdown() { void ProviderRepository::PrepareAndInitializeProvider( const std::optional domain, - std::shared_ptr new_provider, EvaluationContext ctx, + std::shared_ptr new_provider, const EvaluationContext& ctx, bool waitForInit) { std::shared_ptr new_status_manager; std::shared_ptr old_status_manager; @@ -146,7 +152,7 @@ void ProviderRepository::PrepareAndInitializeProvider( void ProviderRepository::InitializeProvider( std::shared_ptr new_status_manager, std::shared_ptr old_status_manager, - EvaluationContext ctx) { + const EvaluationContext& ctx) { if (new_status_manager->GetStatus() == ProviderStatus::kNotReady) { new_status_manager->Init(ctx); } diff --git a/openfeature/provider_repository.h b/openfeature/provider_repository.h index 1ea853f..000dd6a 100644 --- a/openfeature/provider_repository.h +++ b/openfeature/provider_repository.h @@ -36,12 +36,12 @@ class ProviderRepository { // Set the default provider. void SetProvider(std::shared_ptr provider, - EvaluationContext ctx, bool waitForInit); + const EvaluationContext& ctx, bool waitForInit); // Add a provider for a domain. void SetProvider(std::string_view domain, std::shared_ptr provider, - EvaluationContext ctx, bool waitForInit); + const EvaluationContext& ctx, bool waitForInit); // Fetch the status of a provider for a domain. // If the domain is not set, return the default provider status. @@ -54,13 +54,13 @@ class ProviderRepository { private: void PrepareAndInitializeProvider( const std::optional domain, - std::shared_ptr new_provider, EvaluationContext ctx, - bool waitForInit); + std::shared_ptr new_provider, + const EvaluationContext& ctx, bool waitForInit); void InitializeProvider( std::shared_ptr new_status_manager, std::shared_ptr old_status_manager, - EvaluationContext ctx); + const EvaluationContext& ctx); void ShutdownOldProvider( std::shared_ptr old_status_manager); diff --git a/test/provider_repository_test.cpp b/test/provider_repository_test.cpp index 3900e80..44e8683 100644 --- a/test/provider_repository_test.cpp +++ b/test/provider_repository_test.cpp @@ -186,6 +186,17 @@ TEST_F(ProviderRepositoryTest, SetProviderWithFailedInitSetsErrorStatus) { EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kError); } +// Test that getting status after shutdown does not crash and returns a safe +// value. +TEST_F(ProviderRepositoryTest, GetProviderStatusAfterShutdownReturnsNotReady) { + ASSERT_NE(repo.GetProvider(), nullptr); + ASSERT_EQ(repo.GetProviderStatus(), ProviderStatus::kReady); + + repo.Shutdown(); + + EXPECT_EQ(repo.GetProviderStatus(), ProviderStatus::kNotReady); +} + // Test to verify the old provider is shutdown after a new one is ready. TEST_F(ProviderRepositoryTest, OldProviderIsShutdownAfterNewOneIsReady) { std::shared_ptr mock_provider1 = From 46266ab8418d78a4003c01f8abc56fe4c263a012 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Thu, 11 Dec 2025 13:32:45 +0000 Subject: [PATCH 3/5] Fix comments Signed-off-by: NeaguGeorgiana23 --- openfeature/provider_repository.cpp | 34 +++++++++++++-------- openfeature/provider_repository.h | 20 ++++++++++--- test/provider_repository_test.cpp | 46 ++++++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 18 deletions(-) diff --git a/openfeature/provider_repository.cpp b/openfeature/provider_repository.cpp index 40501be..a84dfbc 100644 --- a/openfeature/provider_repository.cpp +++ b/openfeature/provider_repository.cpp @@ -1,15 +1,13 @@ #include "openfeature/provider_repository.h" #include -#include -#include +#include "absl/status/statusor.h" #include "openfeature/noop_provider.h" namespace openfeature { ProviderRepository::ProviderRepository() { - // Initialize with a NoopProvider by default, which is always ready. auto noop_provider = std::make_shared(); auto status_manager = FeatureProviderStatusManager::Create(noop_provider); if (status_manager.ok()) { @@ -22,25 +20,23 @@ ProviderRepository::ProviderRepository() { ProviderRepository::~ProviderRepository() { Shutdown(); } std::shared_ptr -ProviderRepository::GetFeatureProviderStatusManager(std::string_view domain) { +ProviderRepository::GetFeatureProviderStatusManager(std::string_view domain) const { std::shared_lock lock(repo_mutex_); if (domain.empty()) { return default_manager_; } - // Look up domain in map. auto it = provider_manager_.find(std::string(domain)); if (it != provider_manager_.end()) { return it->second; } - // Fallback to default if no provider was found. return default_manager_; } -std::shared_ptr ProviderRepository::GetProvider( - std::string_view domain) { +std::shared_ptr ProviderRepository::GetProvider ( + std::string_view domain) const{ auto manager = GetFeatureProviderStatusManager(domain); if (manager) { @@ -78,7 +74,7 @@ void ProviderRepository::SetProvider(std::string_view domain, waitForInit); } -ProviderStatus ProviderRepository::GetProviderStatus(std::string_view domain) { +ProviderStatus ProviderRepository::GetProviderStatus(std::string_view domain) const{ auto manager = GetFeatureProviderStatusManager(domain); if (manager) { return manager->GetStatus(); @@ -87,6 +83,16 @@ ProviderStatus ProviderRepository::GetProviderStatus(std::string_view domain) { } void ProviderRepository::Shutdown() { + { + std::lock_guard lock(threads_mutex_); + for (auto& thread : initialization_threads_) { + if (thread.joinable()) { + thread.join(); + } + } + initialization_threads_.clear(); + } + std::unique_lock lock(repo_mutex_); if (default_manager_) { @@ -95,6 +101,7 @@ void ProviderRepository::Shutdown() { } for (auto& pair : provider_manager_) { + // Check if the provider is still active before shutting down. if (pair.second->GetStatus() != ProviderStatus::kNotReady) { pair.second->Shutdown(); } @@ -139,13 +146,14 @@ void ProviderRepository::PrepareAndInitializeProvider( } } // Release the lock before running initialization logic. - // Decide whether to initialize sync or async. if (waitForInit) { InitializeProvider(new_status_manager, old_status_manager, ctx); } else { - std::thread([this, new_status_manager, old_status_manager, ctx] { - InitializeProvider(new_status_manager, old_status_manager, ctx); - }).detach(); + std::lock_guard lock(threads_mutex_); + initialization_threads_.emplace_back( + [this, new_status_manager, old_status_manager, ctx] { + InitializeProvider(new_status_manager, old_status_manager, ctx); + }); } } diff --git a/openfeature/provider_repository.h b/openfeature/provider_repository.h index 000dd6a..0f806df 100644 --- a/openfeature/provider_repository.h +++ b/openfeature/provider_repository.h @@ -2,11 +2,14 @@ #define CPP_SDK_INCLUDE_OPENFEATURE_PROVIDER_REPOSITORY_H_ #include +#include #include #include #include #include +#include #include +#include #include "absl/status/status.h" #include "openfeature/evaluation_context.h" @@ -16,6 +19,10 @@ namespace openfeature { +// ProviderRepository holds a default provider and allows for registering named +// providers associated with specific domains. The repository is responsible for +// the initialization and shutdown of providers and provides thread-safe access +// to them. class ProviderRepository { public: ProviderRepository(); @@ -27,12 +34,12 @@ class ProviderRepository { // If the domain is empty, fetch status manager for the default provider. // Otherwise, fetch the status manager for a specific provider. std::shared_ptr GetFeatureProviderStatusManager( - std::string_view domain = ""); + std::string_view domain = "") const; // If the domain is empty then GetProvider returns the default // FeatureProvider. Otherwise it returns the FeatureProvider for the // domain. - std::shared_ptr GetProvider(std::string_view domain = ""); + std::shared_ptr GetProvider(std::string_view domain = "") const; // Set the default provider. void SetProvider(std::shared_ptr provider, @@ -46,9 +53,11 @@ class ProviderRepository { // Fetch the status of a provider for a domain. // If the domain is not set, return the default provider status. // If not found, return the default. - ProviderStatus GetProviderStatus(std::string_view domain = ""); + ProviderStatus GetProviderStatus(std::string_view domain = "") const; - // Shuts down all registered providers and clears the repository. + // Wait for any pending initialization threads to complete before shutting + // down providers. Afterwards, shuts down all registered providers and + // clears the repository. void Shutdown(); private: @@ -76,6 +85,9 @@ class ProviderRepository { provider_manager_; std::shared_ptr default_manager_; mutable std::shared_mutex repo_mutex_; + + std::vector initialization_threads_; + std::mutex threads_mutex_; }; } // namespace openfeature diff --git a/test/provider_repository_test.cpp b/test/provider_repository_test.cpp index 44e8683..b23cdd2 100644 --- a/test/provider_repository_test.cpp +++ b/test/provider_repository_test.cpp @@ -5,7 +5,6 @@ #include #include -#include #include "mocks/mock_feature_provider.h" #include "openfeature/noop_provider.h" @@ -271,6 +270,51 @@ TEST_F(ProviderRepositoryTest, ShutdownAllProviders) { EXPECT_EQ(repo.GetFeatureProviderStatusManager(), nullptr); } +// Test to verify that Shutdown waits for asynchronous initialization to finish. +TEST_F(ProviderRepositoryTest, ShutdownWaitsForAsyncInitializationToComplete) { + std::shared_ptr mock_provider = + std::make_shared(); + + std::promise init_started_promise; + std::future init_started_future = init_started_promise.get_future(); + + std::promise init_can_complete_promise; + std::future init_can_complete_future = + init_can_complete_promise.get_future(); + + // Mock the Init() call. + EXPECT_CALL(*mock_provider, Init(_)).WillOnce([&](const auto&) { + init_started_promise.set_value(); + init_can_complete_future.get(); + return absl::OkStatus(); + }); + + EXPECT_CALL(*mock_provider, Shutdown()).WillOnce(Return(absl::OkStatus())); + + repo.SetProvider(mock_provider, ctx, false); + + // Wait until the background thread is confirmed to be inside the Init() + // method. + init_started_future.wait(); + + // Run Shutdown() to verify that it blocks until init is allowed to complete. + auto shutdown_future = + std::async(std::launch::async, [&]() { repo.Shutdown(); }); + + // We expect it to time out because the Init() is still blocked. + auto status = shutdown_future.wait_for(std::chrono::milliseconds(100)); + ASSERT_EQ(status, std::future_status::timeout) + << "Shutdown() did not wait for initialization to complete."; + + // Unblock Init(). + init_can_complete_promise.set_value(); + + // Shutdown should complete promptly. + shutdown_future.get(); + + SUCCEED() << "Shutdown() correctly waited for the async task."; +} + // Test that setting an existing provider in a new location reuses the manager. TEST_F(ProviderRepositoryTest, SetExistingProviderReusesManager) { std::shared_ptr mock_provider = From ce527a8cbbc89626b1689bf330d15a6e5efd8252 Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Thu, 11 Dec 2025 13:32:45 +0000 Subject: [PATCH 4/5] Fix comments Signed-off-by: NeaguGeorgiana23 --- openfeature/provider_repository.cpp | 34 +++++++++++++-------- openfeature/provider_repository.h | 21 ++++++++++--- test/provider_repository_test.cpp | 46 ++++++++++++++++++++++++++++- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/openfeature/provider_repository.cpp b/openfeature/provider_repository.cpp index 40501be..0422d32 100644 --- a/openfeature/provider_repository.cpp +++ b/openfeature/provider_repository.cpp @@ -1,15 +1,13 @@ #include "openfeature/provider_repository.h" #include -#include -#include +#include "absl/status/statusor.h" #include "openfeature/noop_provider.h" namespace openfeature { ProviderRepository::ProviderRepository() { - // Initialize with a NoopProvider by default, which is always ready. auto noop_provider = std::make_shared(); auto status_manager = FeatureProviderStatusManager::Create(noop_provider); if (status_manager.ok()) { @@ -22,25 +20,24 @@ ProviderRepository::ProviderRepository() { ProviderRepository::~ProviderRepository() { Shutdown(); } std::shared_ptr -ProviderRepository::GetFeatureProviderStatusManager(std::string_view domain) { +ProviderRepository::GetFeatureProviderStatusManager( + std::string_view domain) const { std::shared_lock lock(repo_mutex_); if (domain.empty()) { return default_manager_; } - // Look up domain in map. auto it = provider_manager_.find(std::string(domain)); if (it != provider_manager_.end()) { return it->second; } - // Fallback to default if no provider was found. return default_manager_; } std::shared_ptr ProviderRepository::GetProvider( - std::string_view domain) { + std::string_view domain) const { auto manager = GetFeatureProviderStatusManager(domain); if (manager) { @@ -78,7 +75,8 @@ void ProviderRepository::SetProvider(std::string_view domain, waitForInit); } -ProviderStatus ProviderRepository::GetProviderStatus(std::string_view domain) { +ProviderStatus ProviderRepository::GetProviderStatus( + std::string_view domain) const { auto manager = GetFeatureProviderStatusManager(domain); if (manager) { return manager->GetStatus(); @@ -87,6 +85,16 @@ ProviderStatus ProviderRepository::GetProviderStatus(std::string_view domain) { } void ProviderRepository::Shutdown() { + { + std::lock_guard lock(threads_mutex_); + for (auto& thread : initialization_threads_) { + if (thread.joinable()) { + thread.join(); + } + } + initialization_threads_.clear(); + } + std::unique_lock lock(repo_mutex_); if (default_manager_) { @@ -95,6 +103,7 @@ void ProviderRepository::Shutdown() { } for (auto& pair : provider_manager_) { + // Check if the provider is still active before shutting down. if (pair.second->GetStatus() != ProviderStatus::kNotReady) { pair.second->Shutdown(); } @@ -139,13 +148,14 @@ void ProviderRepository::PrepareAndInitializeProvider( } } // Release the lock before running initialization logic. - // Decide whether to initialize sync or async. if (waitForInit) { InitializeProvider(new_status_manager, old_status_manager, ctx); } else { - std::thread([this, new_status_manager, old_status_manager, ctx] { - InitializeProvider(new_status_manager, old_status_manager, ctx); - }).detach(); + std::lock_guard lock(threads_mutex_); + initialization_threads_.emplace_back( + [this, new_status_manager, old_status_manager, ctx] { + InitializeProvider(new_status_manager, old_status_manager, ctx); + }); } } diff --git a/openfeature/provider_repository.h b/openfeature/provider_repository.h index 000dd6a..6db23d4 100644 --- a/openfeature/provider_repository.h +++ b/openfeature/provider_repository.h @@ -2,11 +2,14 @@ #define CPP_SDK_INCLUDE_OPENFEATURE_PROVIDER_REPOSITORY_H_ #include +#include #include #include #include #include +#include #include +#include #include "absl/status/status.h" #include "openfeature/evaluation_context.h" @@ -16,6 +19,10 @@ namespace openfeature { +// ProviderRepository holds a default provider and allows for registering named +// providers associated with specific domains. The repository is responsible for +// the initialization and shutdown of providers and provides thread-safe access +// to them. class ProviderRepository { public: ProviderRepository(); @@ -27,12 +34,13 @@ class ProviderRepository { // If the domain is empty, fetch status manager for the default provider. // Otherwise, fetch the status manager for a specific provider. std::shared_ptr GetFeatureProviderStatusManager( - std::string_view domain = ""); + std::string_view domain = "") const; // If the domain is empty then GetProvider returns the default // FeatureProvider. Otherwise it returns the FeatureProvider for the // domain. - std::shared_ptr GetProvider(std::string_view domain = ""); + std::shared_ptr GetProvider( + std::string_view domain = "") const; // Set the default provider. void SetProvider(std::shared_ptr provider, @@ -46,9 +54,11 @@ class ProviderRepository { // Fetch the status of a provider for a domain. // If the domain is not set, return the default provider status. // If not found, return the default. - ProviderStatus GetProviderStatus(std::string_view domain = ""); + ProviderStatus GetProviderStatus(std::string_view domain = "") const; - // Shuts down all registered providers and clears the repository. + // Wait for any pending initialization threads to complete before shutting + // down providers. Afterwards, shuts down all registered providers and + // clears the repository. void Shutdown(); private: @@ -76,6 +86,9 @@ class ProviderRepository { provider_manager_; std::shared_ptr default_manager_; mutable std::shared_mutex repo_mutex_; + + std::vector initialization_threads_; + std::mutex threads_mutex_; }; } // namespace openfeature diff --git a/test/provider_repository_test.cpp b/test/provider_repository_test.cpp index 44e8683..b23cdd2 100644 --- a/test/provider_repository_test.cpp +++ b/test/provider_repository_test.cpp @@ -5,7 +5,6 @@ #include #include -#include #include "mocks/mock_feature_provider.h" #include "openfeature/noop_provider.h" @@ -271,6 +270,51 @@ TEST_F(ProviderRepositoryTest, ShutdownAllProviders) { EXPECT_EQ(repo.GetFeatureProviderStatusManager(), nullptr); } +// Test to verify that Shutdown waits for asynchronous initialization to finish. +TEST_F(ProviderRepositoryTest, ShutdownWaitsForAsyncInitializationToComplete) { + std::shared_ptr mock_provider = + std::make_shared(); + + std::promise init_started_promise; + std::future init_started_future = init_started_promise.get_future(); + + std::promise init_can_complete_promise; + std::future init_can_complete_future = + init_can_complete_promise.get_future(); + + // Mock the Init() call. + EXPECT_CALL(*mock_provider, Init(_)).WillOnce([&](const auto&) { + init_started_promise.set_value(); + init_can_complete_future.get(); + return absl::OkStatus(); + }); + + EXPECT_CALL(*mock_provider, Shutdown()).WillOnce(Return(absl::OkStatus())); + + repo.SetProvider(mock_provider, ctx, false); + + // Wait until the background thread is confirmed to be inside the Init() + // method. + init_started_future.wait(); + + // Run Shutdown() to verify that it blocks until init is allowed to complete. + auto shutdown_future = + std::async(std::launch::async, [&]() { repo.Shutdown(); }); + + // We expect it to time out because the Init() is still blocked. + auto status = shutdown_future.wait_for(std::chrono::milliseconds(100)); + ASSERT_EQ(status, std::future_status::timeout) + << "Shutdown() did not wait for initialization to complete."; + + // Unblock Init(). + init_can_complete_promise.set_value(); + + // Shutdown should complete promptly. + shutdown_future.get(); + + SUCCEED() << "Shutdown() correctly waited for the async task."; +} + // Test that setting an existing provider in a new location reuses the manager. TEST_F(ProviderRepositoryTest, SetExistingProviderReusesManager) { std::shared_ptr mock_provider = From 0b140ad75f30754683fb8ef39b9079b01601f93d Mon Sep 17 00:00:00 2001 From: NeaguGeorgiana23 Date: Mon, 5 Jan 2026 11:43:59 +0000 Subject: [PATCH 5/5] Resolve comments Signed-off-by: NeaguGeorgiana23 --- openfeature/provider_repository.cpp | 38 +++++++++++++++++------------ test/provider_repository_test.cpp | 2 -- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/openfeature/provider_repository.cpp b/openfeature/provider_repository.cpp index 0422d32..9ae404b 100644 --- a/openfeature/provider_repository.cpp +++ b/openfeature/provider_repository.cpp @@ -8,8 +8,10 @@ namespace openfeature { ProviderRepository::ProviderRepository() { - auto noop_provider = std::make_shared(); - auto status_manager = FeatureProviderStatusManager::Create(noop_provider); + std::shared_ptr noop_provider = + std::make_shared(); + absl::StatusOr> status_manager = + FeatureProviderStatusManager::Create(noop_provider); if (status_manager.ok()) { std::unique_lock lock(repo_mutex_); default_manager_ = std::move(status_manager.value()); @@ -38,7 +40,8 @@ ProviderRepository::GetFeatureProviderStatusManager( std::shared_ptr ProviderRepository::GetProvider( std::string_view domain) const { - auto manager = GetFeatureProviderStatusManager(domain); + std::shared_ptr manager = + GetFeatureProviderStatusManager(domain); if (manager) { return manager->GetProvider(); @@ -48,36 +51,37 @@ std::shared_ptr ProviderRepository::GetProvider( void ProviderRepository::SetProvider(std::shared_ptr provider, const EvaluationContext& ctx, - bool waitForInit) { + bool wait_for_init) { if (!provider) { std::cerr << "Provider cannot be null" << std::endl; return; } PrepareAndInitializeProvider(std::nullopt, std::move(provider), ctx, - waitForInit); + wait_for_init); } void ProviderRepository::SetProvider(std::string_view domain, std::shared_ptr provider, const EvaluationContext& ctx, - bool waitForInit) { + bool wait_for_init) { if (!provider) { std::cerr << "Provider cannot be null" << std::endl; return; } if (domain.empty()) { - SetProvider(std::move(provider), ctx, waitForInit); + SetProvider(std::move(provider), ctx, wait_for_init); return; } PrepareAndInitializeProvider(std::string(domain), std::move(provider), ctx, - waitForInit); + wait_for_init); } ProviderStatus ProviderRepository::GetProviderStatus( std::string_view domain) const { - auto manager = GetFeatureProviderStatusManager(domain); + std::shared_ptr manager = + GetFeatureProviderStatusManager(domain); if (manager) { return manager->GetStatus(); } @@ -87,7 +91,7 @@ ProviderStatus ProviderRepository::GetProviderStatus( void ProviderRepository::Shutdown() { { std::lock_guard lock(threads_mutex_); - for (auto& thread : initialization_threads_) { + for (std::thread& thread : initialization_threads_) { if (thread.joinable()) { thread.join(); } @@ -102,7 +106,9 @@ void ProviderRepository::Shutdown() { default_manager_.reset(); } - for (auto& pair : provider_manager_) { + for (std::pair>& pair : + provider_manager_) { // Check if the provider is still active before shutting down. if (pair.second->GetStatus() != ProviderStatus::kNotReady) { pair.second->Shutdown(); @@ -114,16 +120,18 @@ void ProviderRepository::Shutdown() { void ProviderRepository::PrepareAndInitializeProvider( const std::optional domain, std::shared_ptr new_provider, const EvaluationContext& ctx, - bool waitForInit) { + bool wait_for_init) { std::shared_ptr new_status_manager; std::shared_ptr old_status_manager; { // Scoping for the unique_lock. std::unique_lock lock(repo_mutex_); - auto existing_manager = GetExistingStatusManagerForProvider(new_provider); + std::shared_ptr existing_manager = + GetExistingStatusManagerForProvider(new_provider); if (!existing_manager) { - auto manager = FeatureProviderStatusManager::Create(new_provider); + absl::StatusOr> manager = + FeatureProviderStatusManager::Create(new_provider); if (!manager.ok()) { std::cerr << "Failed to create FeatureProviderStatusManager: " << manager.status() << std::endl; @@ -148,7 +156,7 @@ void ProviderRepository::PrepareAndInitializeProvider( } } // Release the lock before running initialization logic. - if (waitForInit) { + if (wait_for_init) { InitializeProvider(new_status_manager, old_status_manager, ctx); } else { std::lock_guard lock(threads_mutex_); diff --git a/test/provider_repository_test.cpp b/test/provider_repository_test.cpp index b23cdd2..4f960f4 100644 --- a/test/provider_repository_test.cpp +++ b/test/provider_repository_test.cpp @@ -37,8 +37,6 @@ TEST_F(ProviderRepositoryTest, std::shared_ptr initial_default_manager = repo.GetFeatureProviderStatusManager(); ASSERT_NE(initial_default_manager, nullptr); - ASSERT_EQ(initial_default_manager->GetProvider()->GetMetadata().name, - "Noop Provider"); // Asking for a non-existent domain should return the default manager. std::shared_ptr non_existent_manager =