Skip to content

Commit c50e0d1

Browse files
authored
feat: Add the ability to persist and restore flag configuration. (#93)
1 parent 76647b1 commit c50e0d1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2274
-583
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ option(BUILD_TESTING "Enable C++ unit tests." ON)
2626
option(TESTING_SANITIZERS "Enable sanitizers for unit tests." ON)
2727

2828
if (BUILD_TESTING)
29+
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_GLIBCXX_DEBUG")
2930
add_compile_definitions(LAUNCHDARKLY_USE_ASSERT)
3031
if (TESTING_SANITIZERS)
3132
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")

apps/hello-cpp/main.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include <launchdarkly/context_builder.hpp>
66

7+
#include <filesystem>
8+
#include <fstream>
79
#include <iostream>
810

911
namespace net = boost::asio; // from <boost/asio.hpp>
@@ -13,8 +15,56 @@ using launchdarkly::LogLevel;
1315
using launchdarkly::client_side::Client;
1416
using launchdarkly::client_side::ConfigBuilder;
1517
using launchdarkly::client_side::DataSourceBuilder;
18+
using launchdarkly::client_side::PersistenceBuilder;
1619
using launchdarkly::config::shared::builders::LoggingBuilder;
1720

21+
class FilePersistence : public IPersistence {
22+
public:
23+
FilePersistence(std::string directory) : directory_(std::move(directory)) {
24+
std::filesystem::create_directories(directory_);
25+
}
26+
void Set(std::string storage_namespace,
27+
std::string key,
28+
std::string data) noexcept override {
29+
try {
30+
std::ofstream file;
31+
file.open(MakePath(storage_namespace, key));
32+
file << data;
33+
file.close();
34+
} catch (...) {
35+
std::cout << "Problem writing" << std::endl;
36+
}
37+
}
38+
39+
void Remove(std::string storage_namespace,
40+
std::string key) noexcept override {
41+
std::filesystem::remove(MakePath(storage_namespace, key));
42+
}
43+
44+
std::optional<std::string> Read(std::string storage_namespace,
45+
std::string key) noexcept override {
46+
auto path = MakePath(storage_namespace, key);
47+
48+
try {
49+
if (std::filesystem::exists(path)) {
50+
std::ifstream file(path);
51+
std::stringstream buffer;
52+
buffer << file.rdbuf();
53+
return buffer.str();
54+
}
55+
} catch (...) {
56+
std::cout << "Problem reading" << std::endl;
57+
}
58+
return std::nullopt;
59+
}
60+
61+
private:
62+
std::string MakePath(std::string storage_namespace, std::string key) {
63+
return directory_ + "/" + storage_namespace + "_" + key;
64+
}
65+
std::string directory_;
66+
};
67+
1868
int main() {
1969
net::io_context ioc;
2070

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

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

104+
auto before_init = client.BoolVariationDetail("my-boolean-flag", false);
105+
// This should be the cached version from our persistence, if the
106+
// persistence is populated.
107+
std::cout << "Before Init Complete: " << *before_init << std::endl;
108+
52109
std::cout << "Initial Status: " << client.DataSourceStatus().Status()
53110
<< std::endl;
54111

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

128+
// Identify a series of contexts.
129+
for (auto context_index = 0; context_index < 4; context_index++) {
130+
std::cout << "Identifying user: "
131+
<< "ryan" << context_index << std::endl;
132+
auto future = client.IdentifyAsync(
133+
ContextBuilder()
134+
.kind("user", "ryan" + std::to_string(context_index))
135+
.build());
136+
auto before_ident =
137+
client.BoolVariationDetail("my-boolean-flag", false);
138+
future.get();
139+
auto after_ident = client.BoolVariationDetail("my-boolean-flag", false);
140+
141+
std::cout << "For: "
142+
<< "ryan" << context_index << ": "
143+
<< "Before ident complete: " << *before_init
144+
<< " After: " << *after_ident << std::endl;
145+
146+
sleep(1);
147+
}
148+
71149
// Sit around.
72150
std::cout << "Press enter to exit" << std::endl << std::endl;
73151
std::cin.get();

libs/client-sdk/src/CMakeLists.txt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@ file(GLOB HEADER_LIST CONFIGURE_DEPENDS
88
add_library(${LIBNAME}
99
${HEADER_LIST}
1010
data_sources/streaming_data_source.cpp
11-
data_sources/base_64.cpp
1211
data_sources/data_source_event_handler.cpp
1312
data_sources/data_source_update_sink.cpp
1413
data_sources/polling_data_source.cpp
15-
flag_manager/flag_manager.cpp
14+
flag_manager/flag_store.cpp
1615
flag_manager/flag_updater.cpp
1716
flag_manager/flag_change_event.cpp
1817
data_sources/data_source_status.cpp
@@ -22,7 +21,6 @@ add_library(${LIBNAME}
2221
boost_signal_connection.cpp
2322
client_impl.cpp
2423
client.cpp
25-
data_sources/base_64.hpp
2624
boost_signal_connection.hpp
2725
client_impl.hpp
2826
data_sources/data_source.hpp
@@ -33,9 +31,14 @@ add_library(${LIBNAME}
3331
data_sources/streaming_data_source.hpp
3432
event_processor/event_processor.hpp
3533
event_processor/null_event_processor.hpp
36-
flag_manager/flag_manager.hpp
34+
flag_manager/flag_store.hpp
3735
flag_manager/flag_updater.hpp
3836
event_processor.hpp
37+
flag_manager/context_index.cpp
38+
serialization/json_all_flags.hpp
39+
serialization/json_all_flags.cpp
40+
flag_manager/flag_manager.cpp
41+
flag_manager/flag_persistence.cpp
3942
bindings/c/sdk.cpp)
4043

4144
target_link_libraries(${LIBNAME}

libs/client-sdk/src/client_impl.cpp

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11

22
#include <chrono>
3+
34
#include <optional>
45
#include <utility>
56

67
#include "client_impl.hpp"
78
#include "data_sources/polling_data_source.hpp"
89
#include "data_sources/streaming_data_source.hpp"
10+
911
#include "event_processor/event_processor.hpp"
1012
#include "event_processor/null_event_processor.hpp"
1113

12-
#include <launchdarkly/config/shared/built/logging.hpp>
14+
#include <launchdarkly/encoding/sha_256.hpp>
1315
#include <launchdarkly/logging/console_backend.hpp>
1416
#include <launchdarkly/logging/null_logger.hpp>
1517

@@ -29,18 +31,18 @@ static std::shared_ptr<IDataSource> MakeDataSource(
2931
Config const& config,
3032
Context const& context,
3133
boost::asio::any_io_executor const& executor,
32-
flag_manager::FlagUpdater& flag_updater,
34+
IDataSourceUpdateSink& flag_updater,
3335
data_sources::DataSourceStatusManager& status_manager,
3436
Logger& logger) {
3537
if (config.DataSourceConfig().method.index() == 0) {
3638
// TODO: use initial reconnect delay.
3739
return std::make_shared<
3840
launchdarkly::client_side::data_sources::StreamingDataSource>(
39-
config, executor, context, &flag_updater, status_manager, logger);
41+
config, executor, context, flag_updater, status_manager, logger);
4042
}
4143
return std::make_shared<
4244
launchdarkly::client_side::data_sources::PollingDataSource>(
43-
config, executor, context, &flag_updater, status_manager, logger);
45+
config, executor, context, flag_updater, status_manager, logger);
4446
}
4547

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

59+
static std::shared_ptr<IPersistence> MakePersistence(Config const& config) {
60+
auto persistence = config.Persistence();
61+
if (persistence.disable_persistence) {
62+
return nullptr;
63+
}
64+
return persistence.implementation;
65+
}
66+
5767
ClientImpl::ClientImpl(Config config, Context context)
5868
: config_(config),
5969
logger_(MakeLogger(config.Logging())),
6070
ioc_(kAsioConcurrencyHint),
6171
context_(std::move(context)),
72+
flag_manager_(config.SdkKey(),
73+
logger_,
74+
config.Persistence().max_contexts_,
75+
MakePersistence(config)),
6276
data_source_factory_([this]() {
6377
return MakeDataSource(config_, context_, ioc_.get_executor(),
64-
flag_updater_, status_manager_, logger_);
78+
flag_manager_.Updater(), status_manager_,
79+
logger_);
6580
}),
6681
data_source_(data_source_factory_()),
6782
event_processor_(nullptr),
68-
flag_updater_(flag_manager_),
6983
initialized_(false),
7084
eval_reasons_available_(config.DataSourceConfig().with_reasons) {
85+
flag_manager_.LoadCache(context_);
86+
7187
if (config.Events().Enabled()) {
7288
event_processor_ = std::make_unique<EventProcessor>(ioc_.get_executor(),
7389
config, logger_);
@@ -103,7 +119,7 @@ bool ClientImpl::Initialized() const {
103119

104120
std::unordered_map<Client::FlagKey, Value> ClientImpl::AllFlags() const {
105121
std::unordered_map<Client::FlagKey, Value> result;
106-
for (auto& [key, descriptor] : flag_manager_.GetAll()) {
122+
for (auto& [key, descriptor] : flag_manager_.Store().GetAll()) {
107123
if (descriptor->flag) {
108124
result.try_emplace(key, descriptor->flag->detail().value());
109125
}
@@ -140,6 +156,7 @@ void ClientImpl::FlushAsync() {
140156
}
141157

142158
std::future<void> ClientImpl::IdentifyAsync(Context context) {
159+
flag_manager_.LoadCache(context);
143160
auto identify_promise = std::make_shared<std::promise<void>>();
144161
auto fut = identify_promise->get_future();
145162
data_source_->ShutdownAsync(
@@ -161,7 +178,7 @@ EvaluationDetail<T> ClientImpl::VariationInternal(FlagKey const& key,
161178
Value default_value,
162179
bool check_type,
163180
bool detailed) {
164-
auto desc = flag_manager_.Get(key);
181+
auto desc = flag_manager_.Store().Get(key);
165182

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

208225
} else if (!Initialized()) {
209-
LD_LOG(logger_, LogLevel::kWarn)
226+
LD_LOG(logger_, LogLevel::kInfo)
210227
<< "LaunchDarkly client has not yet been initialized. "
211228
"Returning cached value";
212229
}
@@ -308,7 +325,7 @@ data_sources::IDataSourceStatusProvider& ClientImpl::DataSourceStatus() {
308325
}
309326

310327
flag_manager::IFlagNotifier& ClientImpl::FlagNotifier() {
311-
return flag_updater_;
328+
return flag_manager_.Notifier();
312329
}
313330

314331
void ClientImpl::WaitForReadySync(std::chrono::milliseconds timeout) {

libs/client-sdk/src/client_impl.hpp

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
#include "data_sources/data_source_status_manager.hpp"
2626
#include "event_processor.hpp"
2727
#include "flag_manager/flag_manager.hpp"
28-
#include "flag_manager/flag_updater.hpp"
2928

3029
namespace launchdarkly::client_side {
3130
class ClientImpl : public IClient {
@@ -109,17 +108,15 @@ class ClientImpl : public IClient {
109108

110109
void UpdateContextSynchronized(Context context);
111110

112-
void OnDataSourceShutdown(Context context,
113-
std::function<void()> user_completion);
114-
111+
Logger logger_;
115112
Config config_;
116113

117-
Logger logger_;
118114
boost::asio::io_context ioc_;
119115

120116
Context context_;
121117
mutable std::shared_mutex context_mutex_;
122118

119+
flag_manager::FlagManager flag_manager_;
123120
std::function<std::shared_ptr<IDataSource>()> data_source_factory_;
124121

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

133130
data_sources::DataSourceStatusManager status_manager_;
134-
flag_manager::FlagManager flag_manager_;
135-
flag_manager::FlagUpdater flag_updater_;
136131

137132
std::thread thread_;
138133

0 commit comments

Comments
 (0)