diff --git a/libs/server-sdk/src/client_impl.cpp b/libs/server-sdk/src/client_impl.cpp index 0c53440d7..64d9f17b3 100644 --- a/libs/server-sdk/src/client_impl.cpp +++ b/libs/server-sdk/src/client_impl.cpp @@ -186,11 +186,19 @@ AllFlagsState ClientImpl::AllFlagsState(Context const& context, std::unordered_map 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}; @@ -418,7 +426,16 @@ EvaluationDetail ClientImpl::VariationInternal( std::optional 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; diff --git a/libs/server-sdk/src/data_interfaces/system/idata_system.hpp b/libs/server-sdk/src/data_interfaces/system/idata_system.hpp index 0edf778db..73b1eefef 100644 --- a/libs/server-sdk/src/data_interfaces/system/idata_system.hpp +++ b/libs/server-sdk/src/data_interfaces/system/idata_system.hpp @@ -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; diff --git a/libs/server-sdk/src/data_systems/lazy_load/lazy_load_system.hpp b/libs/server-sdk/src/data_systems/lazy_load/lazy_load_system.hpp index 9584f60d6..f1730fafa 100644 --- a/libs/server-sdk/src/data_systems/lazy_load/lazy_load_system.hpp +++ b/libs/server-sdk/src/data_systems/lazy_load/lazy_load_system.hpp @@ -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; diff --git a/libs/server-sdk/tests/lazy_load_system_test.cpp b/libs/server-sdk/tests/lazy_load_system_test.cpp index f908a8ac8..c2b99bd45 100644 --- a/libs/server-sdk/tests/lazy_load_system_test.cpp +++ b/libs/server-sdk/tests/lazy_load_system_test.cpp @@ -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()); +}