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
27 changes: 22 additions & 5 deletions libs/server-sdk/src/client_impl.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#include "client_impl.hpp"

#include "all_flags_state/all_flags_state_builder.hpp"
Expand Down Expand Up @@ -186,11 +186,19 @@
std::unordered_map<Client::FlagKey, Value> result;

if (!Initialized()) {
LD_LOG(logger_, LogLevel::kWarn)
<< "AllFlagsState() called before client has finished "
"initializing. Data source not available. Returning empty state";
if (data_system_->CanEvaluateWhenNotInitialized()) {
LD_LOG(logger_, LogLevel::kWarn)
<< "AllFlagsState() called before LaunchDarkly client "
"initialization completed; using last known values "
"from data store";
} else {
LD_LOG(logger_, LogLevel::kWarn)
<< "AllFlagsState() called before client has finished "
"initializing. Data source not available. Returning "
"empty state";

return {};
return {};
}
}

AllFlagsStateBuilder builder{options};
Expand Down Expand Up @@ -418,7 +426,16 @@
std::optional<enum EvaluationReason::ErrorKind> ClientImpl::PreEvaluationChecks(
Context const& context) const {
if (!Initialized()) {
return EvaluationReason::ErrorKind::kClientNotReady;
if (data_system_->CanEvaluateWhenNotInitialized()) {
LD_LOG(logger_, LogLevel::kWarn)
<< "Evaluation called before LaunchDarkly client "
"initialization completed; using last known values "
"from data store. The $inited key was not found in "
"the store; typically a Relay Proxy or other SDK "
"should set this key.";
} else {
return EvaluationReason::ErrorKind::kClientNotReady;
}
}
if (!context.Valid()) {
return EvaluationReason::ErrorKind::kUserNotSpecified;
Expand Down
16 changes: 16 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 @@ -21,6 +21,22 @@ class IDataSystem : public IStore {
*/
virtual void Initialize() = 0;

/**
* @brief Returns true if the data system is capable of serving
* flag evaluations even when Initialized() returns false.
*
* This is the case for Lazy Load (daemon mode), where data can be
* fetched on-demand from the persistent store regardless of whether
* the $inited key has been set. In contrast, Background Sync
* cannot serve evaluations until initial data is received.
*
* When this returns true, the evaluation path should log a warning
* (rather than returning CLIENT_NOT_READY) if Initialized() is false.
*/
[[nodiscard]] virtual bool CanEvaluateWhenNotInitialized() const {
return false;
}

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 @@ -58,6 +58,10 @@ class LazyLoad final : public data_interfaces::IDataSystem {

bool Initialized() const override;

[[nodiscard]] bool CanEvaluateWhenNotInitialized() const override {
return true;
}

// Public for usage in tests.
struct Kinds {
static integrations::FlagKind const Flag;
Expand Down
12 changes: 12 additions & 0 deletions libs/server-sdk/tests/lazy_load_system_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -338,3 +338,15 @@ TEST_F(LazyLoadTest, InitializeCalledAgainAfterTTL) {
ASSERT_TRUE(lazy_load.Initialized());
}
}

TEST_F(LazyLoadTest, CanEvaluateWhenNotInitialized) {
built::LazyLoadConfig const config{
built::LazyLoadConfig::EvictionPolicy::Disabled,
std::chrono::seconds(10), mock_reader};

data_systems::LazyLoad const lazy_load(logger, config, status_manager);

// LazyLoad can always serve evaluations on demand, even if not
// initialized (i.e. $inited key not found in store).
ASSERT_TRUE(lazy_load.CanEvaluateWhenNotInitialized());
}
Loading