Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1e0748c
feat: Add the ability to persist and restore flag configuration.
kinyoklion May 16, 2023
667bed4
Flags can be exported.
kinyoklion May 16, 2023
6c7c005
Progress
kinyoklion May 16, 2023
bec8466
Start getting things inplace.
kinyoklion May 16, 2023
cd617a1
Hashing.
kinyoklion May 16, 2023
c34b462
Untested functionality.
kinyoklion May 17, 2023
28603ce
Move context index.
kinyoklion May 17, 2023
a7ad607
Move encoding to internal
kinyoklion May 17, 2023
515b705
Add context index tests.
kinyoklion May 17, 2023
466e6dc
Cleanup.
kinyoklion May 17, 2023
bf62cb3
Persistence tests.
kinyoklion May 17, 2023
eff6d70
Add logger.
kinyoklion May 17, 2023
0e7001a
Add config.
kinyoklion May 17, 2023
905402c
Working sample
kinyoklion May 17, 2023
2d98903
Add extra blank line.
kinyoklion May 17, 2023
821c698
Add blank line.
kinyoklion May 17, 2023
4e6f290
Add blank line.
kinyoklion May 17, 2023
eccee36
Tidy import
kinyoklion May 17, 2023
ac2f149
Format code.
kinyoklion May 17, 2023
8903b70
Change log message to info.
kinyoklion May 17, 2023
4a9ea37
Add eviction test.
kinyoklion May 18, 2023
89e5540
Merge branch 'main' into rlamb/sc-202753/persistence-interface
kinyoklion May 18, 2023
544de96
Persistence working with identify.
kinyoklion May 18, 2023
8a99a0f
Include header for size_t.
kinyoklion May 18, 2023
d652ad5
Construction order.
kinyoklion May 18, 2023
fb4c818
Use base64 encoded sha256.
kinyoklion May 18, 2023
64abd62
Support new builder.
kinyoklion May 18, 2023
62caf41
C bindings for persistence.
kinyoklion May 18, 2023
34fbe2d
Remove Value from names.
kinyoklion May 18, 2023
28dbfbd
Try data instead of begin for windows.
kinyoklion May 18, 2023
f1e87cd
PR feedback.
kinyoklion May 20, 2023
7b0e47e
Merge branch 'main' into rlamb/sc-202753/persistence-interface
kinyoklion May 20, 2023
acdfb21
Fix C errors.
kinyoklion May 20, 2023
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 CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ option(BUILD_TESTING "Enable C++ unit tests." ON)
option(TESTING_SANITIZERS "Enable sanitizers for unit tests." ON)

if (BUILD_TESTING)
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG")
add_compile_definitions(LAUNCHDARKLY_USE_ASSERT)
if (TESTING_SANITIZERS)
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
Expand Down
78 changes: 78 additions & 0 deletions apps/hello-cpp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include <launchdarkly/context_builder.hpp>

#include <filesystem>
#include <fstream>
#include <iostream>

namespace net = boost::asio; // from <boost/asio.hpp>
Expand All @@ -13,8 +15,56 @@ using launchdarkly::LogLevel;
using launchdarkly::client_side::Client;
using launchdarkly::client_side::ConfigBuilder;
using launchdarkly::client_side::DataSourceBuilder;
using launchdarkly::client_side::PersistenceBuilder;
using launchdarkly::config::shared::builders::LoggingBuilder;

class FilePersistence : public IPersistence {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simple persistence implementation for demonstration purposes.

public:
FilePersistence(std::string directory) : directory_(std::move(directory)) {
std::filesystem::create_directories(directory_);
}
void Set(std::string storage_namespace,
std::string key,
std::string data) noexcept override {
try {
std::ofstream file;
file.open(MakePath(storage_namespace, key));
file << data;
file.close();
} catch (...) {
std::cout << "Problem writing" << std::endl;
}
}

void Remove(std::string storage_namespace,
std::string key) noexcept override {
std::filesystem::remove(MakePath(storage_namespace, key));
}

std::optional<std::string> Read(std::string storage_namespace,
std::string key) noexcept override {
auto path = MakePath(storage_namespace, key);

try {
if (std::filesystem::exists(path)) {
std::ifstream file(path);
std::stringstream buffer;
buffer << file.rdbuf();
return buffer.str();
}
} catch (...) {
std::cout << "Problem reading" << std::endl;
}
return std::nullopt;
}

private:
std::string MakePath(std::string storage_namespace, std::string key) {
return directory_ + "/" + storage_namespace + "_" + key;
}
std::string directory_;
};

int main() {
net::io_context ioc;

Expand All @@ -39,6 +89,8 @@ int main() {
config_builder.Logging().Logging(
LoggingBuilder::BasicLogging().Level(LogLevel::kDebug));
config_builder.Events().FlushInterval(std::chrono::seconds(5));
config_builder.Persistence().Custom(
std::make_shared<FilePersistence>("ld_persist"));

auto config = config_builder.Build();
if (!config) {
Expand All @@ -49,6 +101,11 @@ int main() {
Client client(std::move(*config),
ContextBuilder().kind("user", "ryan").build());

auto before_init = client.BoolVariationDetail("my-boolean-flag", false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Read a flag before init is complete. Result can be different if the value was persisted.

// This should be the cached version from our persistence, if the
// persistence is populated.
std::cout << "Before Init Complete: " << *before_init << std::endl;

std::cout << "Initial Status: " << client.DataSourceStatus().Status()
<< std::endl;

Expand All @@ -68,6 +125,27 @@ int main() {
std::cout << "Reason was: " << *reason << std::endl;
}

// Identify a series of contexts.
for (auto context_index = 0; context_index < 4; context_index++) {
std::cout << "Identifying user: "
<< "ryan" << context_index << std::endl;
auto future = client.IdentifyAsync(
ContextBuilder()
.kind("user", "ryan" + std::to_string(context_index))
.build());
auto before_ident =
client.BoolVariationDetail("my-boolean-flag", false);
future.get();
auto after_ident = client.BoolVariationDetail("my-boolean-flag", false);

std::cout << "For: "
<< "ryan" << context_index << ": "
<< "Before ident complete: " << *before_init
<< " After: " << *after_ident << std::endl;

sleep(1);
}

// Sit around.
std::cout << "Press enter to exit" << std::endl << std::endl;
std::cin.get();
Expand Down
11 changes: 7 additions & 4 deletions libs/client-sdk/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@ file(GLOB HEADER_LIST CONFIGURE_DEPENDS
add_library(${LIBNAME}
${HEADER_LIST}
data_sources/streaming_data_source.cpp
data_sources/base_64.cpp
data_sources/data_source_event_handler.cpp
data_sources/data_source_update_sink.cpp
data_sources/polling_data_source.cpp
flag_manager/flag_manager.cpp
flag_manager/flag_store.cpp
flag_manager/flag_updater.cpp
flag_manager/flag_change_event.cpp
data_sources/data_source_status.cpp
Expand All @@ -22,7 +21,6 @@ add_library(${LIBNAME}
boost_signal_connection.cpp
client_impl.cpp
client.cpp
data_sources/base_64.hpp
boost_signal_connection.hpp
client_impl.hpp
data_sources/data_source.hpp
Expand All @@ -33,9 +31,14 @@ add_library(${LIBNAME}
data_sources/streaming_data_source.hpp
event_processor/event_processor.hpp
event_processor/null_event_processor.hpp
flag_manager/flag_manager.hpp
flag_manager/flag_store.hpp
flag_manager/flag_updater.hpp
event_processor.hpp
flag_manager/context_index.cpp
serialization/json_all_flags.hpp
serialization/json_all_flags.cpp
flag_manager/flag_manager.cpp
flag_manager/flag_persistence.cpp
bindings/c/sdk.cpp)

target_link_libraries(${LIBNAME}
Expand Down
37 changes: 27 additions & 10 deletions libs/client-sdk/src/client_impl.cpp
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@

#include <chrono>

#include <optional>
#include <utility>

#include "client_impl.hpp"
#include "data_sources/polling_data_source.hpp"
#include "data_sources/streaming_data_source.hpp"

#include "event_processor/event_processor.hpp"
#include "event_processor/null_event_processor.hpp"

#include <launchdarkly/config/shared/built/logging.hpp>
#include <launchdarkly/encoding/sha_256.hpp>
#include <launchdarkly/logging/console_backend.hpp>
#include <launchdarkly/logging/null_logger.hpp>

Expand All @@ -29,18 +31,18 @@ static std::shared_ptr<IDataSource> MakeDataSource(
Config const& config,
Context const& context,
boost::asio::any_io_executor const& executor,
flag_manager::FlagUpdater& flag_updater,
IDataSourceUpdateSink& flag_updater,
data_sources::DataSourceStatusManager& status_manager,
Logger& logger) {
if (config.DataSourceConfig().method.index() == 0) {
// TODO: use initial reconnect delay.
return std::make_shared<
launchdarkly::client_side::data_sources::StreamingDataSource>(
config, executor, context, &flag_updater, status_manager, logger);
config, executor, context, flag_updater, status_manager, logger);
}
return std::make_shared<
launchdarkly::client_side::data_sources::PollingDataSource>(
config, executor, context, &flag_updater, status_manager, logger);
config, executor, context, flag_updater, status_manager, logger);
}

static Logger MakeLogger(config::shared::built::Logging const& config) {
Expand All @@ -54,20 +56,34 @@ static Logger MakeLogger(config::shared::built::Logging const& config) {
std::make_shared<logging::ConsoleBackend>(config.level, config.tag)};
}

static std::shared_ptr<IPersistence> MakePersistence(Config const& config) {
auto persistence = config.Persistence();
if (persistence.disable_persistence) {
return nullptr;
}
return persistence.implementation;
}

ClientImpl::ClientImpl(Config config, Context context)
: config_(config),
logger_(MakeLogger(config.Logging())),
ioc_(kAsioConcurrencyHint),
context_(std::move(context)),
flag_manager_(config.SdkKey(),
logger_,
config.Persistence().max_contexts_,
MakePersistence(config)),
data_source_factory_([this]() {
return MakeDataSource(config_, context_, ioc_.get_executor(),
flag_updater_, status_manager_, logger_);
flag_manager_.Updater(), status_manager_,
logger_);
}),
data_source_(data_source_factory_()),
event_processor_(nullptr),
flag_updater_(flag_manager_),
initialized_(false),
eval_reasons_available_(config.DataSourceConfig().with_reasons) {
flag_manager_.LoadCache(context_);

if (config.Events().Enabled()) {
event_processor_ = std::make_unique<EventProcessor>(ioc_.get_executor(),
config, logger_);
Expand Down Expand Up @@ -103,7 +119,7 @@ bool ClientImpl::Initialized() const {

std::unordered_map<Client::FlagKey, Value> ClientImpl::AllFlags() const {
std::unordered_map<Client::FlagKey, Value> result;
for (auto& [key, descriptor] : flag_manager_.GetAll()) {
for (auto& [key, descriptor] : flag_manager_.Store().GetAll()) {
if (descriptor->flag) {
result.try_emplace(key, descriptor->flag->detail().value());
}
Expand Down Expand Up @@ -140,6 +156,7 @@ void ClientImpl::FlushAsync() {
}

std::future<void> ClientImpl::IdentifyAsync(Context context) {
flag_manager_.LoadCache(context);
auto identify_promise = std::make_shared<std::promise<void>>();
auto fut = identify_promise->get_future();
data_source_->ShutdownAsync(
Expand All @@ -161,7 +178,7 @@ EvaluationDetail<T> ClientImpl::VariationInternal(FlagKey const& key,
Value default_value,
bool check_type,
bool detailed) {
auto desc = flag_manager_.Get(key);
auto desc = flag_manager_.Store().Get(key);

events::client::FeatureEventParams event = {
std::chrono::system_clock::now(),
Expand Down Expand Up @@ -206,7 +223,7 @@ EvaluationDetail<T> ClientImpl::VariationInternal(FlagKey const& key,
std::move(error_reason));

} else if (!Initialized()) {
LD_LOG(logger_, LogLevel::kWarn)
LD_LOG(logger_, LogLevel::kInfo)
<< "LaunchDarkly client has not yet been initialized. "
"Returning cached value";
}
Expand Down Expand Up @@ -308,7 +325,7 @@ data_sources::IDataSourceStatusProvider& ClientImpl::DataSourceStatus() {
}

flag_manager::IFlagNotifier& ClientImpl::FlagNotifier() {
return flag_updater_;
return flag_manager_.Notifier();
}

void ClientImpl::WaitForReadySync(std::chrono::milliseconds timeout) {
Expand Down
9 changes: 2 additions & 7 deletions libs/client-sdk/src/client_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
#include "data_sources/data_source_status_manager.hpp"
#include "event_processor.hpp"
#include "flag_manager/flag_manager.hpp"
#include "flag_manager/flag_updater.hpp"

namespace launchdarkly::client_side {
class ClientImpl : public IClient {
Expand Down Expand Up @@ -109,17 +108,15 @@ class ClientImpl : public IClient {

void UpdateContextSynchronized(Context context);

void OnDataSourceShutdown(Context context,
std::function<void()> user_completion);

Logger logger_;
Config config_;

Logger logger_;
boost::asio::io_context ioc_;

Context context_;
mutable std::shared_mutex context_mutex_;

flag_manager::FlagManager flag_manager_;
std::function<std::shared_ptr<IDataSource>()> data_source_factory_;

std::shared_ptr<IDataSource> data_source_;
Expand All @@ -131,8 +128,6 @@ class ClientImpl : public IClient {
std::condition_variable init_waiter_;

data_sources::DataSourceStatusManager status_manager_;
flag_manager::FlagManager flag_manager_;
flag_manager::FlagUpdater flag_updater_;

std::thread thread_;

Expand Down
Loading