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
1 change: 1 addition & 0 deletions .bazelversion
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
8.5.1
12 changes: 12 additions & 0 deletions openfeature/BUILD
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
load("@rules_cc//cc:defs.bzl", "cc_library")

package(
default_visibility = ["//visibility:public"],
)
Expand Down Expand Up @@ -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"],
Expand Down
23 changes: 23 additions & 0 deletions openfeature/global_context_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#include "openfeature/global_context_manager.h"

#include <mutex>

namespace openfeature {

GlobalContextManager& GlobalContextManager::GetInstance() {
static GlobalContextManager instance;
return instance;
}

void GlobalContextManager::SetGlobalEvaluationContext(
const EvaluationContext& ctx) {
std::unique_lock<std::shared_mutex> lock(context_mutex_);
global_evaluation_context_ = ctx;
}

EvaluationContext GlobalContextManager::GetGlobalEvaluationContext() const {
std::shared_lock<std::shared_mutex> lock(context_mutex_);
return global_evaluation_context_;
}

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

#include <memory>
#include <shared_mutex>

#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_
11 changes: 11 additions & 0 deletions test/BUILD
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
load("@rules_cc//cc:defs.bzl", "cc_library", "cc_test")

cc_library(
name = "mock_feature_provider",
testonly = True,
Expand Down Expand Up @@ -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",
],
)
89 changes: 89 additions & 0 deletions test/global_context_manager_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#include "openfeature/global_context_manager.h"

#include <gtest/gtest.h>

#include <atomic>
#include <thread>
#include <vector>

#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<bool> 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<std::thread> 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();
}