From b2cfbcdf50ca797ada0aede32ad8ec357a0956e0 Mon Sep 17 00:00:00 2001 From: Bilal Al Date: Mon, 22 Apr 2024 16:18:36 -0700 Subject: [PATCH 1/6] fixed posting data before shutdown --- .../repositories/events/memory_repository.rb | 4 + .../cache/repositories/events_repository.rb | 4 + lib/splitclient-rb/clients/split_client.rb | 14 ++- spec/splitclient/split_client_spec.rb | 118 +++++++++++++----- 4 files changed, 109 insertions(+), 31 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/events/memory_repository.rb b/lib/splitclient-rb/cache/repositories/events/memory_repository.rb index 93339e8c..f38e238f 100644 --- a/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +++ b/lib/splitclient-rb/cache/repositories/events/memory_repository.rb @@ -29,6 +29,10 @@ def clear @adapter.clear end + def empty? + @adapter.empty? + end + def batch return [] if @config.events_queue_size.zero? diff --git a/lib/splitclient-rb/cache/repositories/events_repository.rb b/lib/splitclient-rb/cache/repositories/events_repository.rb index 307f22ea..75c3e7c7 100644 --- a/lib/splitclient-rb/cache/repositories/events_repository.rb +++ b/lib/splitclient-rb/cache/repositories/events_repository.rb @@ -25,6 +25,10 @@ def post_events @config.log_found_exception(__method__.to_s, e) end + def empty? + @repository.empty? + end + protected def metadata diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 322efd30..73b9f978 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -1,3 +1,6 @@ +require 'thread' +require 'thwait' + module SplitIoClient EVENTS_SIZE_THRESHOLD = 32768 EVENT_AVERAGE_SIZE = 1024 @@ -96,7 +99,16 @@ def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}) def destroy @config.logger.info('Split client shutdown started...') if @config.debug_enabled - + if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && !@impressions_repository.empty? && + !@events_repository.empty? && @config.impressions_mode != :none + @config.logger.debug("Impressions and/or Events cache is not empty") + if !@config.threads.key?(:impressions_sender) && !@config.threads.key?(:events_sender) + @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") + threads = [] + threads << @config.threads[:start_sdk] + ThreadsWait.all_waits(*threads) + end + end @config.threads.select { |name, thread| name.to_s.end_with? 'sender' }.values.each do |thread| thread.raise(SplitIoClient::SDKShutdownException) thread.join diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb index c82d225d..2389b367 100644 --- a/spec/splitclient/split_client_spec.rb +++ b/spec/splitclient/split_client_spec.rb @@ -3,40 +3,98 @@ require 'spec_helper' describe SplitIoClient::SplitClient do - let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) } - let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } - let(:impressions_repository) {SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } - let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:events_repository) { SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) } - let(:impression_manager) { SplitIoClient::Engine::Common::ImpressionManager.new(config, impressions_repository, SplitIoClient::Engine::Common::NoopImpressionCounter.new, runtime_producer, SplitIoClient::Observers::NoopImpressionObserver.new, SplitIoClient::Engine::Impressions::NoopUniqueKeysTracker.new) } - let(:evaluation_producer) { SplitIoClient::Telemetry::EvaluationProducer.new(config) } - let(:evaluator) { SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, config) } - let(:split_client) { SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config)) } - - let(:splits) do - File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) - end + context 'split client methods' do + let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) } + let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } + let(:impressions_repository) {SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } + let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } + let(:events_repository) { SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) } + let(:impression_manager) { SplitIoClient::Engine::Common::ImpressionManager.new(config, impressions_repository, SplitIoClient::Engine::Common::NoopImpressionCounter.new, runtime_producer, SplitIoClient::Observers::NoopImpressionObserver.new, SplitIoClient::Engine::Impressions::NoopUniqueKeysTracker.new) } + let(:evaluation_producer) { SplitIoClient::Telemetry::EvaluationProducer.new(config) } + let(:evaluator) { SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, config) } + let(:split_client) { SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config)) } - before do - splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][2]], [], -1) - end + let(:splits) do + File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) + end + + before do + splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:splits][2]], [], -1) + end - it 'check getting treatments' do - expect(split_client.get_treatment('key', 'testing222')).to eq('off') - expect(split_client.get_treatments('key', ['testing222'])).to eq({:testing222 => 'off'}) - expect(split_client.get_treatment_with_config('key', 'testing222')).to eq({:treatment => 'off', :config => nil}) - expect(split_client.get_treatments_with_config('key', ['testing222'])).to eq({:testing222 => {:treatment => 'off', :config => nil}}) - expect(split_client.get_treatments_by_flag_set('key', 'set_1')).to eq({:testing222 => 'off'}) - expect(split_client.get_treatments_by_flag_sets('key', ['set_2'])).to eq({:testing222 => 'off'}) - expect(split_client.get_treatments_with_config_by_flag_set('key', 'set_1')).to eq({:testing222 => {:treatment => 'off', :config => nil}}) - expect(split_client.get_treatments_with_config_by_flag_sets('key', ['set_2'])).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + it 'check getting treatments' do + expect(split_client.get_treatment('key', 'testing222')).to eq('off') + expect(split_client.get_treatments('key', ['testing222'])).to eq({:testing222 => 'off'}) + expect(split_client.get_treatment_with_config('key', 'testing222')).to eq({:treatment => 'off', :config => nil}) + expect(split_client.get_treatments_with_config('key', ['testing222'])).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + expect(split_client.get_treatments_by_flag_set('key', 'set_1')).to eq({:testing222 => 'off'}) + expect(split_client.get_treatments_by_flag_sets('key', ['set_2'])).to eq({:testing222 => 'off'}) + expect(split_client.get_treatments_with_config_by_flag_set('key', 'set_1')).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + expect(split_client.get_treatments_with_config_by_flag_sets('key', ['set_2'])).to eq({:testing222 => {:treatment => 'off', :config => nil}}) + end + + it 'check track' do + expect(split_client.track('key', 'account', 'event', 1)).to eq(true) + end end - it 'check track' do - expect(split_client.track('key', 'account', 'event', 1)).to eq(true) + context 'post data before shutdown' do + let(:splits) do + File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) + end + let(:segment1) do + File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment1.json')) + end + let(:segment2) do + File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment2.json')) + end + let(:segment3) do + File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment3.json')) + end + + it 'posting impressions and events' do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') + .to_return(status: 200, body: splits) + stub_request(:post, 'https://events.split.io/api/events/bulk').to_return(status: 200, body: '') + stub_request(:post, 'https://events.split.io/api/testImpressions/bulk').to_return(status: 200, body: '') + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/config').to_return(status: 200, body: '') + stub_request(:post, 'https://telemetry.split.io/api/v1/metrics/usage').to_return(status: 200, body: '') + stub_request(:get, "https://sdk.split.io/api/splitChanges?since=1506703262916").to_return(status: 200, body: 'ok') + stub_request(:get, "https://sdk.split.io/api/splitChanges?sets=set_3&since=1506703262916").to_return(status: 200, body: 'ok') + mock_segment_changes('segment1', segment1, '-1') + mock_segment_changes('segment1', segment1, '1470947453877') + mock_segment_changes('segment2', segment2, '-1') + mock_segment_changes('segment2', segment2, '1470947453878') + mock_segment_changes('segment3', segment3, '-1') + + factory5 = SplitIoClient::SplitFactory.new('test_api_key', + features_refresh_rate: 9999, + telemetry_refresh_rate: 99999, + impressions_refresh_rate: 99999, + streaming_enabled: false) + client5 = factory5.client + client5.block_until_ready + + for a in 1..100 do + expect(client5.track('id' + a.to_s, 'account', 'event', 1)).to be_truthy + end + expect(client5.instance_variable_get(:@events_repository).empty?).to be(false) + + expect(client5.get_treatment('nico_test', 'FACUNDO_TEST')).to eq 'on' + expect(client5.instance_variable_get(:@impressions_repository).empty?).to be(false) + + client5.destroy() + + expect(client5.instance_variable_get(:@impressions_repository).empty?).to be(true) + expect(client5.instance_variable_get(:@events_repository).empty?).to be(true) + end end +end +def mock_segment_changes(segment_name, segment_json, since) + stub_request(:get, "https://sdk.split.io/api/segmentChanges/#{segment_name}?since=#{since}") + .to_return(status: 200, body: segment_json) end From 4406ef7b4dc2b4a4077aeb391cdca913bd8aea8e Mon Sep 17 00:00:00 2001 From: Bilal Al Date: Mon, 22 Apr 2024 19:58:59 -0700 Subject: [PATCH 2/6] removed thwait lib --- lib/splitclient-rb/clients/split_client.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 73b9f978..858b1acb 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -1,6 +1,3 @@ -require 'thread' -require 'thwait' - module SplitIoClient EVENTS_SIZE_THRESHOLD = 32768 EVENT_AVERAGE_SIZE = 1024 @@ -104,9 +101,7 @@ def destroy @config.logger.debug("Impressions and/or Events cache is not empty") if !@config.threads.key?(:impressions_sender) && !@config.threads.key?(:events_sender) @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") - threads = [] - threads << @config.threads[:start_sdk] - ThreadsWait.all_waits(*threads) + @config.threads[:start_sdk].join end end @config.threads.select { |name, thread| name.to_s.end_with? 'sender' }.values.each do |thread| From be09c7b66dec24710dfb542887b9e492d36d463f Mon Sep 17 00:00:00 2001 From: Bilal Al Date: Mon, 22 Apr 2024 20:47:44 -0700 Subject: [PATCH 3/6] polish --- lib/splitclient-rb/clients/split_client.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 858b1acb..6a6bc55d 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -96,10 +96,10 @@ def get_treatments_with_config_by_flag_sets(key, flag_sets, attributes = {}) def destroy @config.logger.info('Split client shutdown started...') if @config.debug_enabled - if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && !@impressions_repository.empty? && - !@events_repository.empty? && @config.impressions_mode != :none + if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && @config.impressions_mode != :none && + (!@impressions_repository.empty? || !@events_repository.empty?) @config.logger.debug("Impressions and/or Events cache is not empty") - if !@config.threads.key?(:impressions_sender) && !@config.threads.key?(:events_sender) + if !@config.threads.key?(:impressions_sender) || !@config.threads.key?(:events_sender) @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") @config.threads[:start_sdk].join end From bfaf1d29ef6802f201a32a87289acae69d4ebd1a Mon Sep 17 00:00:00 2001 From: Bilal Al Date: Tue, 23 Apr 2024 09:15:11 -0700 Subject: [PATCH 4/6] polish --- lib/splitclient-rb/clients/split_client.rb | 4 +++- spec/splitclient/split_client_spec.rb | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 6a6bc55d..a5ee13cb 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -99,9 +99,11 @@ def destroy if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && @config.impressions_mode != :none && (!@impressions_repository.empty? || !@events_repository.empty?) @config.logger.debug("Impressions and/or Events cache is not empty") + # Adding small delay to ensure sender threads are fully running + sleep(0.1) if !@config.threads.key?(:impressions_sender) || !@config.threads.key?(:events_sender) @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") - @config.threads[:start_sdk].join + @config.threads[:start_sdk].join if @config.threads.key?(:start_sdk) end end @config.threads.select { |name, thread| name.to_s.end_with? 'sender' }.values.each do |thread| diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb index 2389b367..d61922cc 100644 --- a/spec/splitclient/split_client_spec.rb +++ b/spec/splitclient/split_client_spec.rb @@ -72,8 +72,6 @@ factory5 = SplitIoClient::SplitFactory.new('test_api_key', features_refresh_rate: 9999, - telemetry_refresh_rate: 99999, - impressions_refresh_rate: 99999, streaming_enabled: false) client5 = factory5.client client5.block_until_ready From 58512ba77c323fe63bd046a8735428ac4d9e380f Mon Sep 17 00:00:00 2001 From: Bilal Al Date: Tue, 23 Apr 2024 13:20:55 -0700 Subject: [PATCH 5/6] added limit for joining thread --- lib/splitclient-rb/clients/split_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index a5ee13cb..115ccb42 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -108,7 +108,7 @@ def destroy end @config.threads.select { |name, thread| name.to_s.end_with? 'sender' }.values.each do |thread| thread.raise(SplitIoClient::SDKShutdownException) - thread.join + thread.join(5) end @config.threads.values.each { |thread| Thread.kill(thread) } From 4c9e1718f50533e1d49e87053cfcd3aab736b37d Mon Sep 17 00:00:00 2001 From: Bilal Al Date: Wed, 24 Apr 2024 11:16:20 -0700 Subject: [PATCH 6/6] fixed join timeout --- lib/splitclient-rb/clients/split_client.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 115ccb42..2b2eed6c 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -103,12 +103,12 @@ def destroy sleep(0.1) if !@config.threads.key?(:impressions_sender) || !@config.threads.key?(:events_sender) @config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") - @config.threads[:start_sdk].join if @config.threads.key?(:start_sdk) + @config.threads[:start_sdk].join(5) if @config.threads.key?(:start_sdk) end end @config.threads.select { |name, thread| name.to_s.end_with? 'sender' }.values.each do |thread| thread.raise(SplitIoClient::SDKShutdownException) - thread.join(5) + thread.join end @config.threads.values.each { |thread| Thread.kill(thread) }