Skip to content

fix: allow lazy load evaluations when $inited key is not set#508

Draft
kinyoklion wants to merge 2 commits intomainfrom
devin/1773359647-lazy-load-initialized-warning
Draft

fix: allow lazy load evaluations when $inited key is not set#508
kinyoklion wants to merge 2 commits intomainfrom
devin/1773359647-lazy-load-initialized-warning

Conversation

@kinyoklion
Copy link
Member

@kinyoklion kinyoklion commented Mar 12, 2026

Summary

In lazy load / daemon mode, ClientImpl::PreEvaluationChecks gates all flag evaluations on Initialized(). For LazyLoad, Initialized() checks whether the $inited key exists in the persistent store (e.g. Redis). In daemon mode, an external process (Relay Proxy, another SDK) populates the store, but the $inited key may not always be present — causing every evaluation to return CLIENT_NOT_READY even though flag data is available for on-demand fetching.

This PR introduces a CanEvaluateWhenNotInitialized() virtual method on IDataSystem (defaults to false). LazyLoad overrides it to return true, since it can always fetch data on demand. The evaluation path (PreEvaluationChecks and AllFlagsState) now checks this method: when the data system reports it can evaluate while not initialized, a warning is logged and evaluation proceeds instead of blocking.

LazyLoad::Initialized() continues to truthfully report whether $inited is set — consistent with other SDK implementations. The behavioral change is in the evaluation path, not the initialization reporting.

Files changed:

  • idata_system.hpp — adds CanEvaluateWhenNotInitialized() virtual method (default false)
  • lazy_load_system.hpp — overrides CanEvaluateWhenNotInitialized() to return true
  • client_impl.cppPreEvaluationChecks and AllFlagsState warn-and-proceed when data system can evaluate while not initialized
  • lazy_load_system_test.cpp — adds test for CanEvaluateWhenNotInitialized()

Review & Testing Checklist for Human

  • Warning log volume: PreEvaluationChecks logs a warning on every evaluation call when $inited is missing. There is no rate-limiting or "log once" guard. In a busy daemon-mode deployment without $inited, this could produce extremely noisy logs. Consider whether a once-per-session or rate-limited warning is more appropriate.
  • StartAsync() behavior: LazyLoad::Initialize() only sets kValid if Initialized() returns true. When $inited is absent, status stays at kInitializing and the StartAsync() future may never resolve. Verify this is acceptable — evaluations proceed (via the new warning path), but the client may never report as fully "started."
  • AllFlagsState when store is empty/unreachable: Previously returned {} immediately when not initialized. Now for LazyLoad it proceeds to call AllFlags() / AllSegments() on the store. Verify behavior is reasonable if the store is genuinely empty vs. just missing $inited.
  • Contract test verification: This fix addresses Issue feat: eventsource client #1 from PR chore: Add support for persistent store contract tests. #502's persistence test failures. After both PRs are merged, run the persistent data store contract tests end-to-end against a Redis instance to verify daemon-mode tests pass.

Notes

  • This was not built or tested locally (CMake toolchain was not available on the dev machine). CI is the first compilation check.
  • The Erlang SDK uses a similar pattern: it distinguishes not_initialized (blocks) vs store_initialized (warns but proceeds).
  • The Go SDK bypasses the initialization check entirely in daemon mode by using a NullDataSource that always reports initialized.
  • Related: PR #502 (persistence contract tests) — that PR has a separate TTL field name fix; this PR addresses the Initialized() gating issue.

Link to Devin session | Requested by: @kinyoklion

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>
@devin-ai-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Reworked approach based on review feedback: Initialized() should
return false when $inited is not set (consistent with other SDK
implementations), and the evaluation path should handle this case
by warning and proceeding rather than blocking.

Changes:
- Added CanEvaluateWhenNotInitialized() virtual method to IDataSystem
  interface (defaults to false)
- LazyLoad overrides to return true (can serve on demand)
- PreEvaluationChecks warns and proceeds when data system can evaluate
  while not initialized, instead of returning CLIENT_NOT_READY
- AllFlagsState similarly warns and proceeds instead of returning empty
- Reverted LazyLoad::Initialized() to original behavior (truthfully
  reports whether $inited key exists)
- Added unit test for CanEvaluateWhenNotInitialized()

This matches the pattern used in the Erlang SDK where the evaluation
path distinguishes between 'not initialized' (blocks) and
'store initialized' (warns but proceeds).

Co-Authored-By: rlamb@launchdarkly.com <kingdewman@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant