Skip to content
Draft
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 contract-tests/server-contract-tests/src/main.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#include "server.hpp"

Check failure on line 1 in contract-tests/server-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/server-contract-tests/src/main.cpp:1:10 [clang-diagnostic-error]

'server.hpp' file not found

#include <launchdarkly/logging/console_backend.hpp>

Expand All @@ -18,7 +18,7 @@
using launchdarkly::LogLevel;

int main(int argc, char* argv[]) {
launchdarkly::Logger logger{

Check warning on line 21 in contract-tests/server-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/server-contract-tests/src/main.cpp:21:26 [cppcoreguidelines-init-variables]

variable 'logger' is not initialized
std::make_unique<ConsoleBackend>("server-contract-tests")};

std::string const default_port = "8123";
Expand All @@ -31,8 +31,8 @@
try {
net::io_context ioc{1};

auto const p = boost::lexical_cast<unsigned short>(port);

Check warning on line 34 in contract-tests/server-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/server-contract-tests/src/main.cpp:34:20 [readability-identifier-length]

variable name 'p' is too short, expected at least 3 characters
server srv{ioc, "0.0.0.0", p, logger};

Check warning on line 35 in contract-tests/server-contract-tests/src/main.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

contract-tests/server-contract-tests/src/main.cpp:35:16 [cppcoreguidelines-init-variables]

variable 'srv' is not initialized

srv.add_capability("server-side");
srv.add_capability("strongly-typed");
Expand All @@ -50,6 +50,7 @@
srv.add_capability("client-prereq-events");
srv.add_capability("evaluation-hooks");
srv.add_capability("track-hooks");
srv.add_capability("hook-environment-id");
srv.add_capability("wrapper");
srv.add_capability("instance-id");
srv.add_capability("fdv1-fallback");
Expand Down
13 changes: 7 additions & 6 deletions libs/server-sdk/src/client_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
std::vector<std::unique_ptr<data_interfaces::IFDv2InitializerFactory>>
initializer_factories;
for (auto const& initializer : cfg.initializers) {
initializer_factories.push_back(

Check warning on line 97 in libs/server-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

libs/server-sdk/src/client_impl.cpp:97:9 [performance-inefficient-vector-operation]

'push_back' is called inside a loop; consider pre-allocating the container capacity before the loop
std::make_unique<data_systems::FDv2PollingInitializerFactory>(
executor, logger, endpoints, http_properties, initializer));
}
Expand Down Expand Up @@ -220,7 +220,7 @@
bool IsFlagPresent(
std::shared_ptr<data_model::FlagDescriptor> const& flag_desc);

ClientImpl::ClientImpl(Config config, std::string const& version)

Check warning on line 223 in libs/server-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

libs/server-sdk/src/client_impl.cpp:223:31 [performance-unnecessary-value-param]

the parameter 'config' is copied for each invocation but only used as a const reference; consider making it a const reference
: config_(config),
http_properties_(
config::builders::HttpPropertiesBuilder(config.HttpProperties())
Expand All @@ -238,7 +238,7 @@
logger_(MakeLogger(config.Logging())),
ioc_(kAsioConcurrencyHint),
work_(boost::asio::make_work_guard(ioc_)),
status_manager_(),

Check warning on line 241 in libs/server-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

libs/server-sdk/src/client_impl.cpp:241:7 [readability-redundant-member-init]

initializer for member 'status_manager_' is redundant
data_system_(MakeDataSystem(http_properties_,
config_,
ioc_.get_executor(),
Expand All @@ -251,7 +251,7 @@
big_segment_store_(
config_.BigSegments()
? std::make_shared<data_components::BigSegmentStoreWrapper>(
*config_.BigSegments(),

Check warning on line 254 in libs/server-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

libs/server-sdk/src/client_impl.cpp:254:22 [bugprone-unchecked-optional-access]

unchecked access to optional value
ioc_.get_executor(),
logger_)
: nullptr),
Expand Down Expand Up @@ -287,10 +287,10 @@
}

std::future<bool> ClientImpl::StartAsync() {
auto pr = std::make_shared<std::promise<bool>>();

Check warning on line 290 in libs/server-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

libs/server-sdk/src/client_impl.cpp:290:10 [readability-identifier-length]

variable name 'pr' is too short, expected at least 3 characters
auto fut = pr->get_future();

status_manager_.OnDataSourceStatusChangeEx([this, pr](auto _) {

Check warning on line 293 in libs/server-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

libs/server-sdk/src/client_impl.cpp:293:64 [readability-identifier-length]

parameter name '_' is too short, expected at least 3 characters
if (data_system_->Initialized()) {
pr->set_value(true);
return true; /* delete this change listener since the
Expand Down Expand Up @@ -328,7 +328,7 @@
// system to fetch them all at once up-front. This may be a no-op
// depending on the data system (e.g. if the segments are all already in
// memory.)
auto _ = data_system_->AllSegments();

Check warning on line 331 in libs/server-sdk/src/client_impl.cpp

View workflow job for this annotation

GitHub Actions / cpp-linter

libs/server-sdk/src/client_impl.cpp:331:10 [readability-identifier-length]

variable name '_' is too short, expected at least 3 characters

for (auto const& [key, v] : all_flags) {
if (!v || !v->item) {
Expand Down Expand Up @@ -378,8 +378,9 @@
// In this SDK the data is type-safe, and will be enqueued, so it makes
// minimal functional difference.
if (!config_.Hooks().empty()) {
hooks::TrackSeriesContext series_context(
ctx, event_name, metric_value, data, hook_context, std::nullopt);
hooks::TrackSeriesContext series_context(ctx, event_name, metric_value,
data, hook_context,
data_system_->EnvironmentId());
hooks::ExecuteAfterTrack(config_.Hooks(), series_context, logger_);
}

Expand Down Expand Up @@ -487,7 +488,7 @@
if (!config_.Hooks().empty()) {
hooks::EvaluationSeriesContext series_context(
key, context, default_value, method_name, hook_context,
std::nullopt);
data_system_->EnvironmentId());
// Executor only created if there are hooks.
executor.emplace(config_.Hooks(), logger_);
executor->BeforeEvaluation(series_context);
Expand All @@ -501,7 +502,7 @@
if (executor) {
hooks::EvaluationSeriesContext series_context(
key, context, default_value, method_name, hook_context,
std::nullopt);
data_system_->EnvironmentId());
executor->AfterEvaluation(series_context, detail);
}

Expand All @@ -523,7 +524,7 @@
if (executor) {
hooks::EvaluationSeriesContext series_context(
key, context, default_value, method_name, hook_context,
std::nullopt);
data_system_->EnvironmentId());
executor->AfterEvaluation(series_context, detail);
}

Expand All @@ -539,7 +540,7 @@
if (executor) {
hooks::EvaluationSeriesContext series_context(
key, context, default_value, method_name, hook_context,
std::nullopt);
data_system_->EnvironmentId());
executor->AfterEvaluation(series_context, detail);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ void ChangeNotifier::Apply(
}
}

void ChangeNotifier::SetEnvironmentId(std::string environment_id) {
sink_.SetEnvironmentId(std::move(environment_id));
}

bool ChangeNotifier::HasListeners() const {
std::lock_guard lock{signal_mutex_};
return !signals_.empty();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class ChangeNotifier final : public data_interfaces::ITransactionalDestination,
data_model::SegmentDescriptor segment) override;
void Apply(data_model::ChangeSet<data_interfaces::ChangeSetData> change_set)
override;
void SetEnvironmentId(std::string environment_id) override;

[[nodiscard]] std::string const& Identity() const override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ void MemoryStore::Upsert(std::string const& key,
std::make_shared<data_model::SegmentDescriptor>(std::move(segment));
}

void MemoryStore::SetEnvironmentId(std::string environment_id) {
std::lock_guard lock{data_mutex_};
environment_id_ = std::move(environment_id);
}

std::optional<std::string> MemoryStore::EnvironmentId() const {
std::lock_guard lock{data_mutex_};
if (!initialized_) {
return std::nullopt;
}
return environment_id_;
}

bool MemoryStore::RemoveFlag(std::string const& key) {
std::lock_guard lock{data_mutex_};
return flags_.erase(key) == 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <unordered_map>

Expand Down Expand Up @@ -43,6 +44,14 @@ class MemoryStore final : public data_interfaces::IStore,
void Upsert(std::string const& key,
data_model::SegmentDescriptor segment) override;

void SetEnvironmentId(std::string environment_id) override;

/**
* @return The environment ID reported by LaunchDarkly, if any has been
* received.
*/
[[nodiscard]] std::optional<std::string> EnvironmentId() const;

bool RemoveFlag(std::string const& key);

bool RemoveSegment(std::string const& key);
Expand All @@ -66,6 +75,7 @@ class MemoryStore final : public data_interfaces::IStore,
std::shared_ptr<data_model::SegmentDescriptor>>
segments_;
bool initialized_ = false;
std::optional<std::string> environment_id_;
mutable std::mutex data_mutex_;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ void JsonDestination::Upsert(std::string const& key,
dest_.Upsert(Kinds::Segment, key, Serialize(key, segment)));
}

void JsonDestination::SetEnvironmentId(std::string) {}

void JsonDestination::LogUpsertResult(
std::string const& key,
std::string const& data_type,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ class JsonDestination final : public data_interfaces::IDestination {
void Upsert(std::string const& key,
data_model::SegmentDescriptor segment) override;

/**
* @brief No-op; serialized destinations do not store the environment ID.
*/
void SetEnvironmentId(std::string environment_id) override;

/**
* @return Identity of this destination. Used in logs.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ class IDestination {
virtual void Upsert(std::string const& key,
data_model::SegmentDescriptor segment) = 0;

/**
* \brief Record the environment ID that LaunchDarkly reported alongside
* the data. Destinations which do not track it ignore the value.
* \param environment_id The environment ID.
*/
virtual void SetEnvironmentId(std::string environment_id) = 0;

/**
* \return Identity of the destination. Used in logs.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ struct FDv2SourceResult {
* Set if the underlying transport observed an FDv1 fallback directive.
*/
std::optional<FDv1FallbackDirective> fdv1_fallback;

/**
* Set if the underlying transport reported the environment ID (e.g. an
* X-LD-EnvID response header).
*/
std::optional<std::string> environment_id;
};

} // namespace launchdarkly::server_side::data_interfaces
9 changes: 9 additions & 0 deletions libs/server-sdk/src/data_interfaces/system/idata_system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

#include "../store/istore.hpp"

#include <optional>
#include <string>

namespace launchdarkly::server_side::data_interfaces {

/**
Expand All @@ -21,6 +24,12 @@ class IDataSystem : public IStore {
*/
virtual void Initialize() = 0;

/**
* @return The environment ID reported by LaunchDarkly alongside the data,
* if the system has received one.
*/
[[nodiscard]] virtual std::optional<std::string> EnvironmentId() const = 0;

virtual ~IDataSystem() override = default;
IDataSystem(IDataSystem const& item) = delete;
IDataSystem(IDataSystem&& item) = delete;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ std::string const& BackgroundSync::Identity() const {
return id;
}

std::optional<std::string> BackgroundSync::EnvironmentId() const {
return store_.EnvironmentId();
}

std::shared_ptr<data_model::FlagDescriptor> BackgroundSync::GetFlag(
std::string const& key) const {
return store_.GetFlag(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class BackgroundSync final : public data_interfaces::IDataSystem {

bool Initialized() const override;

std::optional<std::string> EnvironmentId() const override;

private:
data_components::MemoryStore store_;
data_components::ChangeNotifier change_notifier_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#include <launchdarkly/encoding/base_64.hpp>
#include <launchdarkly/network/http_error_messages.hpp>

#include <launchdarkly/serialization/json_flag.hpp>
#include <launchdarkly/detail/serialization/json_primitives.hpp>
#include <launchdarkly/serialization/json_flag.hpp>
#include <launchdarkly/serialization/json_sdk_data_set.hpp>
#include <launchdarkly/server_side/data_source_status.hpp>

Expand Down Expand Up @@ -102,6 +102,13 @@ void PollingDataSource::DoPoll() {
}

void PollingDataSource::HandlePollResult(network::HttpResult const& res) {
if (!res.IsError() && (res.Status() == 200 || res.Status() == 304)) {
if (auto const it = res.Headers().find("X-LD-EnvID");
it != res.Headers().end() && !it->second.empty()) {
sink_->SetEnvironmentId(it->second);
}
}

auto header_etag = res.Headers().find("etag");
bool has_etag = header_etag != res.Headers().end();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ void StreamingDataSource::StartAsync(
data_model::SDKDataSet const* bootstrap_data) {
boost::ignore_unused(bootstrap_data);

sink_ = dest;
event_handler_.emplace(*dest, logger_, status_manager_);

status_manager_.SetState(DataSourceStatus::DataSourceState::kInitializing);
Expand Down Expand Up @@ -125,16 +126,31 @@ void StreamingDataSource::StartAsync(

auto weak_self = weak_from_this();

client_builder.on_response(
[weak_self](boost::beast::http::response_header<> const& headers) {
auto self = weak_self.lock();
if (!self || headers.result_int() != 200) {
return;
}
if (auto const it = headers.find("X-LD-EnvID");
it != headers.end() && !it->value().empty()) {
self->sink_->SetEnvironmentId(
std::string(it->value().data(), it->value().size()));
}
});

client_builder.receiver([weak_self](launchdarkly::sse::Event const& event) {
if (auto self = weak_self.lock()) {
auto status =
self->event_handler_->HandleMessage(event.type(), event.data());
if (status == DataSourceEventHandler::MessageStatus::kInvalidMessage) {
if (status ==
DataSourceEventHandler::MessageStatus::kInvalidMessage) {
// Invalid data received - restart the connection with backoff
// to get a fresh stream. The backoff mechanism prevents rapid
// reconnection attempts.
LD_LOG(self->logger_, LogLevel::kWarn)
<< "Received invalid data from stream, restarting connection";
<< "Received invalid data from stream, restarting "
"connection";
if (self->client_) {
self->client_->async_restart("invalid data in stream");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class StreamingDataSource final

config::built::BackgroundSyncConfig::StreamingConfig streaming_config_;

// Destination for all data obtained via streaming. Set by StartAsync.
data_interfaces::IDestination* sink_ = nullptr;

std::shared_ptr<sse::Client> client_;
};
} // namespace launchdarkly::server_side::data_systems
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#include "fdv1_adapter_synchronizer.hpp"

#include <utility>
Expand Down Expand Up @@ -44,6 +44,7 @@
if (closed_future_.IsFinished()) {
return;
}
result.environment_id = environment_id_;
if (pending_promise_) {
promise = std::move(pending_promise_);
pending_promise_.reset();
Expand All @@ -57,6 +58,12 @@
promise->Resolve(std::move(result));
}

void FDv1AdapterSynchronizer::State::SetEnvironmentId(
std::string environment_id) {
std::lock_guard lock(mutex_);
environment_id_ = std::move(environment_id);
}

// ----- ConvertingDestination -----

FDv1AdapterSynchronizer::ConvertingDestination::ConvertingDestination(
Expand Down Expand Up @@ -113,6 +120,13 @@
data_model::Selector{}}}});
}

void FDv1AdapterSynchronizer::ConvertingDestination::SetEnvironmentId(
std::string environment_id) {
if (auto state = state_.lock()) {
state->SetEnvironmentId(std::move(environment_id));
}
}

std::string const& FDv1AdapterSynchronizer::ConvertingDestination::Identity()
const {
static std::string const identity = "FDv1 adapter destination";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ class FDv1AdapterSynchronizer final

void Notify(data_interfaces::FDv2SourceResult result);

// Stamped onto subsequently notified results.
void SetEnvironmentId(std::string environment_id);

private:
// Finished once the owning FDv1AdapterSynchronizer's close_promise_
// is resolved. Read in Notify to drop late results.
Expand All @@ -83,6 +86,7 @@ class FDv1AdapterSynchronizer final
std::optional<async::Promise<data_interfaces::FDv2SourceResult>>
pending_promise_;
std::deque<data_interfaces::FDv2SourceResult> result_queue_;
std::optional<std::string> environment_id_;
};

/**
Expand All @@ -97,6 +101,7 @@ class FDv1AdapterSynchronizer final
data_model::FlagDescriptor flag) override;
void Upsert(std::string const& key,
data_model::SegmentDescriptor segment) override;
void SetEnvironmentId(std::string environment_id) override;
[[nodiscard]] std::string const& Identity() const override;

private:
Expand Down
12 changes: 12 additions & 0 deletions libs/server-sdk/src/data_systems/fdv2/fdv2_data_system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ void FDv2DataSystem::OnInitializerResult(
data_interfaces::FDv2SourceResult result) {
using Result = data_interfaces::FDv2SourceResult;

if (result.environment_id) {
change_notifier_.SetEnvironmentId(*result.environment_id);
}

bool got_basis = false;
bool got_shutdown = false;
bool disconnected = false;
Expand Down Expand Up @@ -350,6 +354,10 @@ void FDv2DataSystem::OnSynchronizerResult(
}
}

if (result.environment_id) {
change_notifier_.SetEnvironmentId(*result.environment_id);
}

bool got_shutdown = false;
bool advance = false;
bool disconnected = false;
Expand Down Expand Up @@ -487,4 +495,8 @@ bool FDv2DataSystem::Initialized() const {
return store_.Initialized();
}

std::optional<std::string> FDv2DataSystem::EnvironmentId() const {
return store_.EnvironmentId();
}

} // namespace launchdarkly::server_side::data_systems
6 changes: 6 additions & 0 deletions libs/server-sdk/src/data_systems/fdv2/fdv2_data_system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,12 @@ class FDv2DataSystem final : public data_interfaces::IDataSystem {
*/
bool Initialized() const override;

/**
* Returns the environment ID reported by LaunchDarkly alongside the data,
* if data has been received and the transport reported one.
*/
std::optional<std::string> EnvironmentId() const override;

private:
/**
* Signals the orchestration loop to stop and closes any active source.
Expand Down
Loading
Loading