Skip to content

Commit e5bf095

Browse files
fix: allow lazy load evaluations when $inited key is not set
In lazy load / daemon mode, the SDK's Initialized() check was blocking all flag evaluations when the $inited key was not found in the persistent store. This is problematic because in daemon mode, an external process (like Relay Proxy) populates the store, and the $inited key may not always be present. The fix changes LazyLoad::Initialized() to always return true, allowing evaluations to proceed using available data. When the underlying source reports not initialized ($inited key not found), a warning is logged to alert operators that a Relay Proxy or other SDK should set this key. This aligns with the Go SDK behavior where daemon mode (ExternalUpdatesOnly) always considers the data source initialized. Updated unit tests to reflect the new behavior and added tests verifying the warning is logged appropriately. Co-Authored-By: rlamb@launchdarkly.com <kingdewman@gmail.com>
1 parent ae832a3 commit e5bf095

3 files changed

Lines changed: 65 additions & 15 deletions

File tree

libs/server-sdk/src/data_systems/lazy_load/lazy_load_system.cpp

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,12 @@ std::string const& LazyLoad::Identity() const {
7979

8080
void LazyLoad::Initialize() {
8181
status_manager_.SetState(DataSourceState::kInitializing);
82-
if (Initialized()) {
83-
status_manager_.SetState(DataSourceState::kValid);
84-
}
82+
// In lazy load mode, we always consider the system ready for
83+
// evaluations. The Initialized() call here will log a warning if
84+
// the underlying source reports not initialized (e.g. $inited key
85+
// not found), but we proceed regardless.
86+
Initialized();
87+
status_manager_.SetState(DataSourceState::kValid);
8588
}
8689

8790
std::shared_ptr<data_model::FlagDescriptor> LazyLoad::GetFlag(
@@ -121,25 +124,35 @@ LazyLoad::AllSegments() const {
121124
}
122125

123126
bool LazyLoad::Initialized() const {
124-
/* Since the memory store isn't provisioned with an initial SDKDataSet
125-
* like in the Background Sync system, we can't forward this call to
126-
* MemoryStore::Initialized(). Instead, we need to check the state of the
127-
* underlying source. */
127+
/* In lazy load mode, the system is always considered initialized for
128+
* the purpose of flag evaluations. Data is fetched on-demand from the
129+
* underlying source regardless of the $inited key state.
130+
*
131+
* However, we still check the underlying source's initialized state
132+
* so we can warn if $inited is not set. A properly configured
133+
* Relay Proxy or other SDK populating the store should set the
134+
* $inited key. */
128135

129136
auto const state = tracker_.State(Keys::kInitialized, time_());
130137
if (initialized_.has_value()) {
131-
/* Once initialized, we can always return true. */
132138
if (initialized_.value()) {
133139
return true;
134140
}
135-
/* If not yet initialized, then we can return false only if the state is
136-
* fresh - otherwise we should make an attempt to refresh. */
137141
if (data_components::ExpirationTracker::TrackState::kFresh == state) {
138-
return false;
142+
return true;
139143
}
140144
}
141145
RefreshInitState();
142-
return initialized_.value_or(false);
146+
if (!initialized_.value_or(false) && !logged_init_warning_) {
147+
LD_LOG(logger_, LogLevel::kWarn)
148+
<< "LazyLoad: data source reports not initialized "
149+
"(the $inited key was not found in the store). "
150+
"Evaluations will proceed using available data. "
151+
"Typically a Relay Proxy or other SDK should set this "
152+
"key; verify your configuration if this is unexpected.";
153+
logged_init_warning_ = true;
154+
}
155+
return true;
143156
}
144157

145158
void LazyLoad::RefreshAllFlags() const {

libs/server-sdk/src/data_systems/lazy_load/lazy_load_system.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ class LazyLoad final : public data_interfaces::IDataSystem {
187187
mutable data_components::ExpirationTracker tracker_;
188188
TimeFn time_;
189189
mutable std::optional<bool> initialized_;
190+
mutable bool logged_init_warning_{false};
190191

191192
ClockType::duration fresh_duration_;
192193

libs/server-sdk/tests/lazy_load_system_test.cpp

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ TEST_F(LazyLoadTest, AllSegmentsRefreshesIndividualSegment) {
283283
ASSERT_EQ(segment2->version, 2);
284284
}
285285

286-
TEST_F(LazyLoadTest, InitializeNotQueriedRepeatedly) {
286+
TEST_F(LazyLoadTest, InitializedReturnsTrueEvenWhenSourceNotInitialized) {
287287
built::LazyLoadConfig const config{
288288
built::LazyLoadConfig::EvictionPolicy::Disabled,
289289
std::chrono::seconds(10), mock_reader};
@@ -292,8 +292,10 @@ TEST_F(LazyLoadTest, InitializeNotQueriedRepeatedly) {
292292

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

295+
// In lazy load mode, Initialized() always returns true even when the
296+
// underlying source reports not initialized ($inited key not found).
295297
for (std::size_t i = 0; i < 10; i++) {
296-
ASSERT_FALSE(lazy_load.Initialized());
298+
ASSERT_TRUE(lazy_load.Initialized());
297299
}
298300
}
299301

@@ -329,12 +331,46 @@ TEST_F(LazyLoadTest, InitializeCalledAgainAfterTTL) {
329331
data_systems::LazyLoad const lazy_load(logger, config, status_manager,
330332
[&]() { return now; });
331333

334+
// Always returns true even when source reports not initialized.
332335
for (std::size_t i = 0; i < 10; i++) {
333-
ASSERT_FALSE(lazy_load.Initialized());
336+
ASSERT_TRUE(lazy_load.Initialized());
334337
now += std::chrono::seconds(1);
335338
}
336339

340+
// Still true after TTL when source now reports initialized.
337341
for (std::size_t i = 0; i < 10; i++) {
338342
ASSERT_TRUE(lazy_load.Initialized());
339343
}
340344
}
345+
346+
TEST_F(LazyLoadTest, InitializedLogsWarningWhenSourceNotInitialized) {
347+
built::LazyLoadConfig const config{
348+
built::LazyLoadConfig::EvictionPolicy::Disabled,
349+
std::chrono::seconds(10), mock_reader};
350+
351+
EXPECT_CALL(*mock_reader, Initialized).WillOnce(Return(false));
352+
353+
data_systems::LazyLoad const lazy_load(logger, config, status_manager);
354+
355+
ASSERT_TRUE(lazy_load.Initialized());
356+
357+
// A warning should have been logged about $inited not being found.
358+
ASSERT_TRUE(spy_logger_backend->Contains(
359+
0, LogLevel::kWarn, "$inited"));
360+
}
361+
362+
TEST_F(LazyLoadTest, InitializedDoesNotLogWarningWhenSourceIsInitialized) {
363+
built::LazyLoadConfig const config{
364+
built::LazyLoadConfig::EvictionPolicy::Disabled,
365+
std::chrono::seconds(10), mock_reader};
366+
367+
EXPECT_CALL(*mock_reader, Initialized).WillOnce(Return(true));
368+
369+
data_systems::LazyLoad const lazy_load(logger, config, status_manager);
370+
371+
ASSERT_TRUE(lazy_load.Initialized());
372+
373+
// No warning should be logged when source is properly initialized.
374+
// Only debug-level messages should be present (or none at all).
375+
ASSERT_TRUE(spy_logger_backend->Count(0));
376+
}

0 commit comments

Comments
 (0)