Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions openfeature/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down
222 changes: 222 additions & 0 deletions openfeature/provider_repository.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#include "openfeature/provider_repository.h"

#include <iostream>

#include "absl/status/statusor.h"
#include "openfeature/noop_provider.h"

namespace openfeature {

ProviderRepository::ProviderRepository() {
std::shared_ptr<openfeature::NoopProvider> noop_provider =
std::make_shared<NoopProvider>();
absl::StatusOr<std::unique_ptr<FeatureProviderStatusManager>> status_manager =
FeatureProviderStatusManager::Create(noop_provider);
if (status_manager.ok()) {
std::unique_lock<std::shared_mutex> lock(repo_mutex_);
default_manager_ = std::move(status_manager.value());
default_manager_->SetStatus(ProviderStatus::kReady);
}
}

ProviderRepository::~ProviderRepository() { Shutdown(); }

std::shared_ptr<FeatureProviderStatusManager>
ProviderRepository::GetFeatureProviderStatusManager(
std::string_view domain) const {
std::shared_lock<std::shared_mutex> lock(repo_mutex_);

if (domain.empty()) {
return default_manager_;
}

auto it = provider_manager_.find(std::string(domain));
if (it != provider_manager_.end()) {
return it->second;
}

return default_manager_;
}

std::shared_ptr<FeatureProvider> ProviderRepository::GetProvider(
std::string_view domain) const {
std::shared_ptr<FeatureProviderStatusManager> manager =
GetFeatureProviderStatusManager(domain);

if (manager) {
return manager->GetProvider();
}
return nullptr;
}

void ProviderRepository::SetProvider(std::shared_ptr<FeatureProvider> provider,
const EvaluationContext& ctx,
bool wait_for_init) {
if (!provider) {
std::cerr << "Provider cannot be null" << std::endl;
return;
}
PrepareAndInitializeProvider(std::nullopt, std::move(provider), ctx,
wait_for_init);
}

void ProviderRepository::SetProvider(std::string_view domain,
std::shared_ptr<FeatureProvider> provider,
const EvaluationContext& ctx,
bool wait_for_init) {
if (!provider) {
std::cerr << "Provider cannot be null" << std::endl;
return;
}

if (domain.empty()) {
SetProvider(std::move(provider), ctx, wait_for_init);
return;
Comment thread
oxddr marked this conversation as resolved.
}

PrepareAndInitializeProvider(std::string(domain), std::move(provider), ctx,
wait_for_init);
}

ProviderStatus ProviderRepository::GetProviderStatus(
std::string_view domain) const {
std::shared_ptr<FeatureProviderStatusManager> manager =
GetFeatureProviderStatusManager(domain);
if (manager) {
return manager->GetStatus();
}
return ProviderStatus::kNotReady;
}

void ProviderRepository::Shutdown() {
{
std::lock_guard<std::mutex> lock(threads_mutex_);
for (std::thread& thread : initialization_threads_) {
if (thread.joinable()) {
thread.join();
}
}
initialization_threads_.clear();
}

std::unique_lock<std::shared_mutex> lock(repo_mutex_);

if (default_manager_) {
default_manager_->Shutdown();
default_manager_.reset();
}

for (std::pair<const std::string,
std::shared_ptr<FeatureProviderStatusManager>>& pair :
provider_manager_) {
// Check if the provider is still active before shutting down.
if (pair.second->GetStatus() != ProviderStatus::kNotReady) {
pair.second->Shutdown();
}
}
provider_manager_.clear();
}

void ProviderRepository::PrepareAndInitializeProvider(
const std::optional<std::string> domain,
std::shared_ptr<FeatureProvider> new_provider, const EvaluationContext& ctx,
bool wait_for_init) {
std::shared_ptr<FeatureProviderStatusManager> new_status_manager;
std::shared_ptr<FeatureProviderStatusManager> old_status_manager;

{ // Scoping for the unique_lock.
std::unique_lock<std::shared_mutex> lock(repo_mutex_);

std::shared_ptr<FeatureProviderStatusManager> existing_manager =
GetExistingStatusManagerForProvider(new_provider);
if (!existing_manager) {
absl::StatusOr<std::unique_ptr<FeatureProviderStatusManager>> 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) {
Comment thread
oxddr marked this conversation as resolved.
// 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.

if (wait_for_init) {
InitializeProvider(new_status_manager, old_status_manager, ctx);
} else {
std::lock_guard<std::mutex> lock(threads_mutex_);
initialization_threads_.emplace_back(
[this, new_status_manager, old_status_manager, ctx] {
InitializeProvider(new_status_manager, old_status_manager, ctx);
});
}
}

void ProviderRepository::InitializeProvider(
std::shared_ptr<FeatureProviderStatusManager> new_status_manager,
std::shared_ptr<FeatureProviderStatusManager> old_status_manager,
const 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<FeatureProviderStatusManager> old_status_manager) {
if (old_status_manager) {
std::shared_lock<std::shared_mutex> lock(repo_mutex_);

if (!IsStatusManagerRegistered(old_status_manager)) {
old_status_manager->Shutdown();
}
}
}

std::shared_ptr<FeatureProviderStatusManager>
ProviderRepository::GetExistingStatusManagerForProvider(
const std::shared_ptr<FeatureProvider>& 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<FeatureProviderStatusManager>& manager) {
if (default_manager_ == manager) {
return true;
}
for (const auto& pair : provider_manager_) {
if (pair.second == manager) {
return true;
}
}
return false;
}

} // namespace openfeature
96 changes: 96 additions & 0 deletions openfeature/provider_repository.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#ifndef CPP_SDK_INCLUDE_OPENFEATURE_PROVIDER_REPOSITORY_H_
#define CPP_SDK_INCLUDE_OPENFEATURE_PROVIDER_REPOSITORY_H_

#include <memory>
#include <mutex>
#include <optional>
#include <shared_mutex>
#include <string>
#include <string_view>
#include <thread>
#include <unordered_map>
#include <vector>

#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 {

// 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 {
Comment thread
NeaguGeorgiana23 marked this conversation as resolved.
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<FeatureProviderStatusManager> GetFeatureProviderStatusManager(
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<FeatureProvider> GetProvider(
std::string_view domain = "") const;

// Set the default provider.
void SetProvider(std::shared_ptr<FeatureProvider> provider,
const EvaluationContext& ctx, bool waitForInit);

// Add a provider for a domain.
void SetProvider(std::string_view domain,
std::shared_ptr<FeatureProvider> provider,
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.
// If not found, return the default.
ProviderStatus GetProviderStatus(std::string_view domain = "") const;

// 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:
void PrepareAndInitializeProvider(
const std::optional<std::string> domain,
std::shared_ptr<FeatureProvider> new_provider,
const EvaluationContext& ctx, bool waitForInit);

void InitializeProvider(
std::shared_ptr<FeatureProviderStatusManager> new_status_manager,
std::shared_ptr<FeatureProviderStatusManager> old_status_manager,
const EvaluationContext& ctx);

void ShutdownOldProvider(
std::shared_ptr<FeatureProviderStatusManager> old_status_manager);

std::shared_ptr<FeatureProviderStatusManager>
GetExistingStatusManagerForProvider(
const std::shared_ptr<FeatureProvider>& provider);

bool IsStatusManagerRegistered(
const std::shared_ptr<FeatureProviderStatusManager>& manager);

std::unordered_map<std::string, std::shared_ptr<FeatureProviderStatusManager>>
provider_manager_;
std::shared_ptr<FeatureProviderStatusManager> default_manager_;
mutable std::shared_mutex repo_mutex_;

std::vector<std::thread> initialization_threads_;
std::mutex threads_mutex_;
};

} // namespace openfeature

#endif // CPP_SDK_INCLUDE_OPENFEATURE_PROVIDER_REPOSITORY_H_
10 changes: 10 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
Loading