From e7ad7a98711a28950befbc75592177bbad93e8a2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:08:48 +0000 Subject: [PATCH 1/2] fix: record FDv2 environment ID before signaling readiness Co-Authored-By: Bee Klimt --- lib/ldclient-rb/impl/data_system/fdv2.rb | 23 ++++++++++++------- spec/impl/data_system/fdv2_datasystem_spec.rb | 13 ++++------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/lib/ldclient-rb/impl/data_system/fdv2.rb b/lib/ldclient-rb/impl/data_system/fdv2.rb index 860b0238..330b8e66 100644 --- a/lib/ldclient-rb/impl/data_system/fdv2.rb +++ b/lib/ldclient-rb/impl/data_system/fdv2.rb @@ -129,7 +129,7 @@ def start # Start the main coordination thread main_thread = Thread.new { run_main_loop } main_thread.name = "FDv2-main" - @threads << main_thread + @lock.synchronize { @threads << main_thread } @ready_event end @@ -148,12 +148,19 @@ def stop end end - # Wait for all threads to complete - @threads.each do |thread| - next unless thread.alive? + # Wait for all threads to complete, including any started while we were joining + joined = [] + loop do + pending = @lock.synchronize { @threads.dup } - joined + break if pending.empty? - thread.join(5.0) # 5 second timeout - @logger.warn { "[LDClient] Thread #{thread.name} did not terminate in time" } if thread.alive? + pending.each do |thread| + joined << thread + next unless thread.alive? + + thread.join(5.0) # 5 second timeout + @logger.warn { "[LDClient] Thread #{thread.name} did not terminate in time" } if thread.alive? + end end # Close the store @@ -333,7 +340,7 @@ def run_synchronizers # Start synchronizer loop in a separate thread sync_thread = Thread.new { synchronizer_loop } sync_thread.name = "FDv2-synchronizers" - @threads << sync_thread + @lock.synchronize { @threads << sync_thread } end # @@ -470,8 +477,8 @@ def consume_synchronizer_results(synchronizer, check_recovery: false) # Set ready event on valid update if update.state == LaunchDarkly::Interfaces::DataSource::Status::VALID - @ready_event.set record_environment_id(update.environment_id) + @ready_event.set end # Update status diff --git a/spec/impl/data_system/fdv2_datasystem_spec.rb b/spec/impl/data_system/fdv2_datasystem_spec.rb index eb724467..0160f98e 100644 --- a/spec/impl/data_system/fdv2_datasystem_spec.rb +++ b/spec/impl/data_system/fdv2_datasystem_spec.rb @@ -441,12 +441,12 @@ def with_data_system(initializers, synchronizers) .build changed = Concurrent::Event.new - changes = [] + flag_keys = Concurrent::Array.new listener = Object.new listener.define_singleton_method(:update) do |flag_change| - changes << flag_change - changed.set if changes.length >= 2 + flag_keys << flag_change.key + changed.set if flag_keys.include?("initialflag") && flag_keys.include?("fdv1replacementflag") end fdv2 = FDv2.new(sdk_key, config, data_system_config) @@ -454,12 +454,7 @@ def with_data_system(initializers, synchronizers) ready_event = fdv2.start expect(ready_event.wait(2)).to be true - expect(changed.wait(3)).to be true - - # Verify we got changes for both flags - flag_keys = changes.map { |change| change.key } - expect(flag_keys).to include("initialflag") - expect(flag_keys).to include("fdv1replacementflag") + expect(changed.wait(3)).to be(true), "expected changes for both flags, got #{flag_keys.to_a}" fdv2.stop end From af5c695b7b6cc1a697a1a946ecba43a2f2320c1a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 8 Sep 2026 21:13:09 +0000 Subject: [PATCH 2/2] fix: assert flag change keys instead of an exact change count Co-Authored-By: Bee Klimt --- spec/impl/data_system/fdv2_datasystem_spec.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/spec/impl/data_system/fdv2_datasystem_spec.rb b/spec/impl/data_system/fdv2_datasystem_spec.rb index 0160f98e..15ba84cc 100644 --- a/spec/impl/data_system/fdv2_datasystem_spec.rb +++ b/spec/impl/data_system/fdv2_datasystem_spec.rb @@ -235,14 +235,12 @@ def with_data_system(initializers, synchronizers) .build changed = Concurrent::Event.new - changes = [] - count = 0 + changes = Concurrent::Array.new listener = Object.new listener.define_singleton_method(:update) do |flag_change| - count += 1 changes << flag_change - changed.set if count == 2 + changed.set if changes.length >= 2 end fdv2 = FDv2.new(sdk_key, config, data_system_config) @@ -254,9 +252,7 @@ def with_data_system(initializers, synchronizers) td.update(td.flag("flagkey").on(false)) expect(changed.wait(2)).to be true - expect(changes.length).to eq(2) - expect(changes[0].key).to eq("flagkey") - expect(changes[1].key).to eq("flagkey") + expect(changes.map(&:key).uniq).to eq(["flagkey"]) fdv2.stop end