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
12 changes: 10 additions & 2 deletions lib/ldclient-rb/ldclient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ def initialize(sdk_key, config = Config.default, wait_for_sec = 5)
# The pid of the process in which the fork warning was last logged, or nil.
@fork_warned_pid = Concurrent::AtomicReference.new(nil)

# Each flag lets the matching cached-data warning log once per client.
@cached_data_evaluation_warned = Concurrent::AtomicBoolean.new(false)
@cached_data_all_flags_warned = Concurrent::AtomicBoolean.new(false)

start_up(wait_for_sec)
end

Expand Down Expand Up @@ -629,7 +633,9 @@ def all_flags_state(context, options={})

unless initialized?
if @data_system.store.initialized?
@config.logger.warn { "Called all_flags_state before client initialization; using last known values from data store" }
if @cached_data_all_flags_warned.make_true
@config.logger.warn { "Called all_flags_state before client initialization; using last known values from data store. This message is logged once." }
end
else
@config.logger.warn { "Called all_flags_state before client initialization. Data store not available; returning empty state" }
return FeatureFlagsState.new(false)
Expand Down Expand Up @@ -765,7 +771,9 @@ def flag_tracker

if @data_system.data_availability != Impl::DataSystem::DataAvailability::REFRESHED
if @data_system.data_availability == Impl::DataSystem::DataAvailability::CACHED
@config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store" }
if @cached_data_evaluation_warned.make_true
@config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store. This message is logged once." }
end
else
@config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
Expand Down
82 changes: 82 additions & 0 deletions spec/ldclient_cached_data_warning_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
require "capturing_logger"
require "mock_components"
require "spec_helper"

module LaunchDarkly
describe "LDClient cached-data warnings" do
let(:logger) { CapturingLogger.new }
let(:context) { basic_context }

# A data source that starts but never reports initialized. With a store that already holds
# data, the client evaluates with cached data.
def uninitialized_source
wait = double
allow(wait).to receive(:wait)
source = double
allow(source).to receive(:start).and_return(wait)
allow(source).to receive(:stop)
allow(source).to receive(:initialized?).and_return(false)
source
end

def initialized_store
store = InMemoryFeatureStore.new
store.init({ Impl::DataStore::FEATURES => {}, Impl::DataStore::SEGMENTS => {} })
store
end

def cached_data_config
test_config(logger: logger, feature_store: initialized_store, data_source: uninitialized_source)
end

def evaluation_warnings(logger)
logger.output.lines.grep(/Client has not finished initializing; using last known values/)
end

def all_flags_state_warnings(logger)
logger.output.lines.grep(/Called all_flags_state before client initialization; using last known values/)
end

it "logs the evaluation warning once per client" do
with_client(cached_data_config) do |client|
client.variation("flag", context, false)
client.variation("flag", context, false)
client.variation_detail("flag", context, false)
expect(evaluation_warnings(logger).length).to eq(1)
end
end

# all_flags_state checks LDClient#initialized?, which is true when the store holds data. The
# stub makes the client report not initialized so the cached-data branch runs.
it "logs the all_flags_state warning once per client" do
with_client(cached_data_config) do |client|
allow(client).to receive(:initialized?).and_return(false)
client.all_flags_state(context)
client.all_flags_state(context)
expect(all_flags_state_warnings(logger).length).to eq(1)
end
end

it "logs the evaluation and all_flags_state warnings independently" do
with_client(cached_data_config) do |client|
allow(client).to receive(:initialized?).and_return(false)
client.variation("flag", context, false)
client.all_flags_state(context)
client.variation("flag", context, false)
client.all_flags_state(context)
expect(evaluation_warnings(logger).length).to eq(1)
expect(all_flags_state_warnings(logger).length).to eq(1)
end
end

it "logs the warning again for a new client" do
with_client(cached_data_config) do |client|
client.variation("flag", context, false)
end
with_client(cached_data_config) do |client|
client.variation("flag", context, false)
end
expect(evaluation_warnings(logger).length).to eq(2)
end
end
end
Loading