From e4f1293c9612d1d95c382d0e075c8f6b1d07372a Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 5 Nov 2025 11:49:20 -0800 Subject: [PATCH 01/46] used connect_nonblock for socket --- lib/splitclient-rb/sse/event_source/client.rb | 16 +++++++++++++--- lib/splitclient-rb/version.rb | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index b58406fb..83e91701 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -156,11 +156,21 @@ def socket_connect ssl_context = OpenSSL::SSL::SSLContext.new ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context) ssl_socket.hostname = @uri.host - ssl_socket.connect - return ssl_socket.connect + + begin + ssl_socket.connect_nonblock + rescue IO::WaitReadable + IO.select([ssl_socket]) + retry + rescue IO::WaitWritable + IO.select(nil, [ssl_socket]) + retry + end + + return ssl_socket +# return ssl_socket.connect rescue Exception => e @config.logger.error("socket connect error: #{e.inspect}") - puts e.inspect return nil end end diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index e7470b9c..046f9d21 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.0-rc1' + VERSION = '8.10.0-rc2' end From 0e0b8e9e9bc5efbb1f917f581fe0fa0f8e6e6c84 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 6 Nov 2025 11:37:59 -0800 Subject: [PATCH 02/46] added more logging --- lib/splitclient-rb/sse/event_source/client.rb | 8 ++++++-- lib/splitclient-rb/version.rb | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index 83e91701..7a357a2b 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -97,16 +97,18 @@ def connect_stream(latch) rescue Timeout::Error => e log_if_debug("SSE read operation timed out!: #{e.inspect}", 3) return Constants::PUSH_RETRYABLE_ERROR - rescue EOFError + rescue EOFError => e + log_if_debug("SSE read operation EOF Exception!: #{e.inspect}", 3) raise 'eof exception' rescue Errno::EAGAIN => e log_if_debug("SSE client transient error: #{e.inspect}", 1) IO.select([tcp_socket]) retry rescue Errno::EBADF, IOError => e - log_if_debug(e.inspect, 3) + log_if_debug("SSE read operation EBADF or IOError: #{e.inspect}", 3) return nil rescue StandardError => e + log_if_debug("SSE read operation StandardError: #{e.inspect}", 3) return nil if ENV['SPLITCLIENT_ENV'] == 'test' log_if_debug("Error reading partial data: #{e.inspect}", 3) @@ -115,6 +117,8 @@ def connect_stream(latch) process_data(partial_data) end + log_if_debug("SSE read operation exited: #{connected}") + nil end diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index 046f9d21..e14904e2 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.0-rc2' + VERSION = '8.10.0-rc3' end From d2da707e7b4282cad79023c9e352f313a2aca936 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 6 Nov 2025 14:15:05 -0800 Subject: [PATCH 03/46] added logging --- lib/splitclient-rb/sse/event_source/client.rb | 6 ++++-- lib/splitclient-rb/version.rb | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index 7a357a2b..be9ac578 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -86,6 +86,7 @@ def connect_thread(latch) def connect_stream(latch) return Constants::PUSH_NONRETRYABLE_ERROR unless socket_write(latch) while connected? || @first_event.value + log_if_debug("Inside coonnect_stream while loop.", 3) begin partial_data = "" Timeout::timeout @read_timeout do @@ -117,7 +118,7 @@ def connect_stream(latch) process_data(partial_data) end - log_if_debug("SSE read operation exited: #{connected}") + log_if_debug("SSE read operation exited: #{connected?}", 3) nil end @@ -146,6 +147,7 @@ def read_first_event(data, latch) if response_code == OK_CODE && !error_event @connected.make_true + @config.logger.debug("SSE client first event Connected is true") @telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::SSE_CONNECTION_ESTABLISHED, nil) push_status(Constants::PUSH_CONNECTED) end @@ -183,9 +185,9 @@ def socket_connect end def process_data(partial_data) + log_if_debug("Event partial data: #{partial_data}", 1) return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE - log_if_debug("Event partial data: #{partial_data}", 1) events = @event_parser.parse(partial_data) events.each { |event| process_event(event) } rescue StandardError => e diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index e14904e2..30095ee4 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.0-rc3' + VERSION = '8.10.0-rc4' end From b41d641e1249363ccd5e6218f4972aa565eaca41 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 6 Nov 2025 22:00:40 -0800 Subject: [PATCH 04/46] replace timeout block with IO call --- lib/splitclient-rb/sse/event_source/client.rb | 55 +++++++++++-------- lib/splitclient-rb/version.rb | 2 +- 2 files changed, 32 insertions(+), 25 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index be9ac578..6b6f87a8 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -87,32 +87,39 @@ def connect_stream(latch) return Constants::PUSH_NONRETRYABLE_ERROR unless socket_write(latch) while connected? || @first_event.value log_if_debug("Inside coonnect_stream while loop.", 3) - begin - partial_data = "" - Timeout::timeout @read_timeout do + if IO.select([@socket], nil, nil, @read_timeout) + begin partial_data = @socket.readpartial(10_000) - end - read_first_event(partial_data, latch) - - raise 'eof exception' if partial_data == :eof - rescue Timeout::Error => e - log_if_debug("SSE read operation timed out!: #{e.inspect}", 3) - return Constants::PUSH_RETRYABLE_ERROR - rescue EOFError => e - log_if_debug("SSE read operation EOF Exception!: #{e.inspect}", 3) - raise 'eof exception' - rescue Errno::EAGAIN => e - log_if_debug("SSE client transient error: #{e.inspect}", 1) - IO.select([tcp_socket]) - retry - rescue Errno::EBADF, IOError => e - log_if_debug("SSE read operation EBADF or IOError: #{e.inspect}", 3) - return nil - rescue StandardError => e - log_if_debug("SSE read operation StandardError: #{e.inspect}", 3) - return nil if ENV['SPLITCLIENT_ENV'] == 'test' + read_first_event(partial_data, latch) - log_if_debug("Error reading partial data: #{e.inspect}", 3) + raise 'eof exception' if partial_data == :eof + rescue IO::WaitReadable => e + log_if_debug("SSE client transient error: #{e.inspect}", 1) + IO.select([@socket], nil, nil, @read_timeout) + retry + rescue Errno::ETIMEDOUT => e + log_if_debug("SSE read operation timed out!: #{e.inspect}", 3) + return Constants::PUSH_RETRYABLE_ERROR + rescue EOFError => e + log_if_debug("SSE read operation EOF Exception!: #{e.inspect}", 3) + raise 'eof exception' + rescue Errno::EAGAIN => e + log_if_debug("SSE client transient error: #{e.inspect}", 1) + IO.select([@socket], nil, nil, @read_timeout) + retry + rescue Errno::EBADF, IOError => e + log_if_debug("SSE read operation EBADF or IOError: #{e.inspect}", 3) + return nil + rescue StandardError => e + log_if_debug("SSE read operation StandardError: #{e.inspect}", 3) + return nil if ENV['SPLITCLIENT_ENV'] == 'test' + + log_if_debug("Error reading partial data: #{e.inspect}", 3) + return Constants::PUSH_RETRYABLE_ERROR + end + else + # Timeout occurred, no data available + log_if_debug("SSE read operation timed out, no data available.", 3) return Constants::PUSH_RETRYABLE_ERROR end diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index 30095ee4..015056f0 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.0-rc4' + VERSION = '8.10.0-rc5' end From 0d0ccdffe4b754bf0305f22aaaf139aa390fb70d Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 7 Nov 2025 11:10:59 -0800 Subject: [PATCH 05/46] added test --- lib/splitclient-rb/sse/event_source/client.rb | 9 ++---- spec/sse/event_source/client_spec.rb | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 6 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index 6b6f87a8..14b54ccb 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -86,7 +86,6 @@ def connect_thread(latch) def connect_stream(latch) return Constants::PUSH_NONRETRYABLE_ERROR unless socket_write(latch) while connected? || @first_event.value - log_if_debug("Inside coonnect_stream while loop.", 3) if IO.select([@socket], nil, nil, @read_timeout) begin partial_data = @socket.readpartial(10_000) @@ -118,14 +117,13 @@ def connect_stream(latch) return Constants::PUSH_RETRYABLE_ERROR end else - # Timeout occurred, no data available - log_if_debug("SSE read operation timed out, no data available.", 3) + @config.logger.debug("SSE read operation timed out, no data available.") return Constants::PUSH_RETRYABLE_ERROR end process_data(partial_data) end - log_if_debug("SSE read operation exited: #{connected?}", 3) + log_if_debug("SSE read operation exited: #{connected?}", 1) nil end @@ -179,9 +177,8 @@ def socket_connect IO.select(nil, [ssl_socket]) retry end - return ssl_socket -# return ssl_socket.connect + rescue Exception => e @config.logger.error("socket connect error: #{e.inspect}") return nil diff --git a/spec/sse/event_source/client_spec.rb b/spec/sse/event_source/client_spec.rb index 1c199b09..213cc184 100644 --- a/spec/sse/event_source/client_spec.rb +++ b/spec/sse/event_source/client_spec.rb @@ -221,6 +221,36 @@ end end + it 'client timeout and reconnect' do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1') + .with(headers: { 'Authorization' => 'Bearer client-spec-key' }) + .to_return(status: 200, body: '{"ff":{"d":[],"s":-1,"t":5564531221}, "rbs":{"d":[],"s":-1,"t":-1}}') + stub_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=5564531221&rbSince=-1') + .with(headers: { 'Authorization' => 'Bearer client-spec-key' }) + .to_return(status: 200, body: '{"ff":{"d":[],"s":5564531221,"t":5564531221}, "rbs":{"d":[],"s":-1,"t":-1}}') + + mock_server do |server| + start_workers + server.setup_response('/') do |_, res| + send_stream_content(res, event_split_update) + end + + sse_client = subject.new(config, api_token, telemetry_runtime_producer, event_parser, notification_manager_keeper, notification_processor, push_status_queue, read_timeout: 0.1) + connected = sse_client.start(server.base_uri) + sleep 1 + expect(connected).to eq(true) + expect(sse_client.connected?).to eq(true) + expect(push_status_queue.pop(true)).to eq(SplitIoClient::Constants::PUSH_CONNECTED) + sleep 3 + expect(log.string).to include 'SSE read operation timed out, no data available' + expect(sse_client.connected?).to eq(true) + sse_client.close + expect(sse_client.connected?).to eq(false) + + stop_workers + end + end + it 'first event - when server return 400' do mock_server do |server| server.setup_response('/') do |_, res| From a746601791ae19061714127a2950af62c56a934d Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 7 Nov 2025 19:29:10 -0800 Subject: [PATCH 06/46] added tests --- lib/splitclient-rb/sse/event_source/client.rb | 44 +++++------- spec/sse/event_source/client_spec.rb | 67 +++++++++++++++++++ 2 files changed, 83 insertions(+), 28 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index 14b54ccb..dacee06f 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -38,7 +38,7 @@ def initialize(config, def close(status = nil) unless connected? - log_if_debug('SSEClient already disconected.', 3) + @config.logger.debug('SSEClient already disconected.') return end @@ -76,10 +76,10 @@ def connected? def connect_thread(latch) @config.threads[:connect_stream] = Thread.new do - log_if_debug('Starting connect_stream thread ...', 2) + @config.logger.info('Starting connect_stream thread ...') new_status = connect_stream(latch) push_status(new_status) - log_if_debug('connect_stream thread finished.', 2) + @config.logger.info('connect_stream thread finished.') end end @@ -93,37 +93,38 @@ def connect_stream(latch) raise 'eof exception' if partial_data == :eof rescue IO::WaitReadable => e - log_if_debug("SSE client transient error: #{e.inspect}", 1) + @config.logger.debug("SSE client transient error: #{e.inspect}") IO.select([@socket], nil, nil, @read_timeout) retry rescue Errno::ETIMEDOUT => e - log_if_debug("SSE read operation timed out!: #{e.inspect}", 3) + @config.logger.error("SSE read operation timed out!: #{e.inspect}") return Constants::PUSH_RETRYABLE_ERROR rescue EOFError => e - log_if_debug("SSE read operation EOF Exception!: #{e.inspect}", 3) + @config.logger.error("SSE read operation EOF Exception!: #{e.inspect}") raise 'eof exception' rescue Errno::EAGAIN => e - log_if_debug("SSE client transient error: #{e.inspect}", 1) + puts "transient error" + @config.logger.debug("SSE client transient error: #{e.inspect}") IO.select([@socket], nil, nil, @read_timeout) retry rescue Errno::EBADF, IOError => e - log_if_debug("SSE read operation EBADF or IOError: #{e.inspect}", 3) + @config.logger.error("SSE read operation EBADF or IOError: #{e.inspect}") return nil rescue StandardError => e - log_if_debug("SSE read operation StandardError: #{e.inspect}", 3) + @config.logger.error("SSE read operation StandardError: #{e.inspect}") return nil if ENV['SPLITCLIENT_ENV'] == 'test' - log_if_debug("Error reading partial data: #{e.inspect}", 3) + @config.logger.error("Error reading partial data: #{e.inspect}") return Constants::PUSH_RETRYABLE_ERROR end else - @config.logger.debug("SSE read operation timed out, no data available.") + @config.logger.error("SSE read operation timed out, no data available.") return Constants::PUSH_RETRYABLE_ERROR end process_data(partial_data) end - log_if_debug("SSE read operation exited: #{connected?}", 1) + @config.logger.info("SSE read operation exited: #{connected?}") nil end @@ -134,7 +135,7 @@ def socket_write(latch) @socket.puts(build_request(@uri)) true rescue StandardError => e - log_if_debug("Error during connecting to #{@uri.host}. Error: #{e.inspect}", 3) + @config.logger.error("Error during connecting to #{@uri.host}. Error: #{e.inspect}") latch.count_down false end @@ -189,7 +190,7 @@ def socket_connect end def process_data(partial_data) - log_if_debug("Event partial data: #{partial_data}", 1) + @config.logger.debug("Event partial data: #{partial_data}") return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE events = @event_parser.parse(partial_data) @@ -207,7 +208,7 @@ def build_request(uri) req << "SplitSDKMachineName: #{@config.machine_name}\r\n" req << "SplitSDKClientKey: #{@api_key.split(//).last(4).join}\r\n" unless @api_key.nil? req << "Cache-Control: no-cache\r\n\r\n" - log_if_debug("Request info: #{req}", 1) + @config.logger.debug("Request info: #{req}") req end @@ -245,19 +246,6 @@ def push_status(status) @config.logger.debug("Pushing new sse status: #{status}") @status_queue.push(status) end - - def log_if_debug(text, level) - if @config.debug_enabled - case level - when 1 - @config.logger.debug(text) - when 2 - @config.logger.info(text) - else - @config.logger.error(text) - end - end - end end end end diff --git a/spec/sse/event_source/client_spec.rb b/spec/sse/event_source/client_spec.rb index 213cc184..9498632c 100644 --- a/spec/sse/event_source/client_spec.rb +++ b/spec/sse/event_source/client_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'http_server_mock' +require 'rspec/mocks' describe SplitIoClient::SSE::EventSource::Client do subject { SplitIoClient::SSE::EventSource::Client } @@ -266,6 +267,72 @@ stop_workers end end + + it 'test exceptions' do + mock_server do |server| + server.setup_response('/') do |_, res| + send_stream_content(res, event_split_update) + end + start_workers + + sse_client = subject.new(config, api_token, telemetry_runtime_producer, event_parser, notification_manager_keeper, notification_processor, push_status_queue) + + sse_client.instance_variable_set(:@uri, URI(server.base_uri)) + latch = Concurrent::CountDownLatch.new(1) + + allow(sse_client).to receive(:read_first_event).and_raise(Errno::ETIMEDOUT) + sse_client.send(:connect_stream, latch) + expect(log.string).to include 'SSE read operation timed out!' + + allow(sse_client).to receive(:read_first_event).and_raise(EOFError) + expect { sse_client.send(:connect_stream, latch) }.to raise_error(RuntimeError) + expect(log.string).to include 'SSE read operation EOF Exception!' + + allow(sse_client).to receive(:read_first_event).and_raise(Errno::EBADF) + sse_client.send(:connect_stream, latch) + expect(log.string).to include 'SSE read operation EBADF or IOError' + + allow(sse_client).to receive(:read_first_event).and_raise(IOError) + sse_client.send(:connect_stream, latch) + expect(log.string).to include 'SSE read operation EBADF or IOError' + + allow(sse_client).to receive(:read_first_event).and_raise(StandardError) + sse_client.send(:connect_stream, latch) + expect(log.string).to include 'SSE read operation StandardError:' + + stop_workers + end + end + + it 'test retry with EAGAIN and IO::WaitReadable exceptions' do + mock_server do |server| + server.setup_response('/') do |_, res| + send_stream_content(res, event_split_update) + end + start_workers + + sse_client = subject.new(config, api_token, telemetry_runtime_producer, event_parser, notification_manager_keeper, notification_processor, push_status_queue) + + sse_client.instance_variable_set(:@uri, URI(server.base_uri)) + latch = Concurrent::CountDownLatch.new(1) + + allow(sse_client).to receive(:read_first_event).and_raise(Errno::EAGAIN) + thr1 = Thread.new do + sse_client.send(:connect_stream, latch) + end + allow(sse_client).to receive(:read_first_event).and_return(true) + expect(thr1.status).to eq('run') + + allow(sse_client).to receive(:read_first_event).and_raise(IO::WaitReadable) + thr2 = Thread.new do + sse_client.send(:connect_stream, latch) + end + allow(sse_client).to receive(:read_first_event).and_return(true) + expect(thr2.status).to eq('run') + + stop_workers + end + end end private From 70ef7d954ac02b55274267d8094a42d98951514b Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 11 Nov 2025 21:55:32 -0800 Subject: [PATCH 07/46] handled EBADF and IOError exceptions and added tests --- lib/splitclient-rb/sse/event_source/client.rb | 11 +++--- spec/sse/event_source/client_spec.rb | 39 +++++++++++++++---- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index dacee06f..4cf498fe 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -93,6 +93,10 @@ def connect_stream(latch) raise 'eof exception' if partial_data == :eof rescue IO::WaitReadable => e + @config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") + IO.select([@socket], nil, nil, @read_timeout) + retry + rescue Errno::EAGAIN => e @config.logger.debug("SSE client transient error: #{e.inspect}") IO.select([@socket], nil, nil, @read_timeout) retry @@ -102,14 +106,9 @@ def connect_stream(latch) rescue EOFError => e @config.logger.error("SSE read operation EOF Exception!: #{e.inspect}") raise 'eof exception' - rescue Errno::EAGAIN => e - puts "transient error" - @config.logger.debug("SSE client transient error: #{e.inspect}") - IO.select([@socket], nil, nil, @read_timeout) - retry rescue Errno::EBADF, IOError => e @config.logger.error("SSE read operation EBADF or IOError: #{e.inspect}") - return nil + return Constants::PUSH_RETRYABLE_ERROR rescue StandardError => e @config.logger.error("SSE read operation StandardError: #{e.inspect}") return nil if ENV['SPLITCLIENT_ENV'] == 'test' diff --git a/spec/sse/event_source/client_spec.rb b/spec/sse/event_source/client_spec.rb index 9498632c..95e0a1f2 100644 --- a/spec/sse/event_source/client_spec.rb +++ b/spec/sse/event_source/client_spec.rb @@ -304,10 +304,10 @@ end end - it 'test retry with EAGAIN and IO::WaitReadable exceptions' do + it 'test retry with EAGAIN exceptions' do mock_server do |server| server.setup_response('/') do |_, res| - send_stream_content(res, event_split_update) + send_stream_content(res, event_occupancy) end start_workers @@ -317,18 +317,41 @@ latch = Concurrent::CountDownLatch.new(1) allow(sse_client).to receive(:read_first_event).and_raise(Errno::EAGAIN) + sleep(1) thr1 = Thread.new do sse_client.send(:connect_stream, latch) end + sleep(1) allow(sse_client).to receive(:read_first_event).and_return(true) - expect(thr1.status).to eq('run') - - allow(sse_client).to receive(:read_first_event).and_raise(IO::WaitReadable) + expect(log.string).to include 'SSE client transient error' + + stop_workers + end + end + + it 'test retry with IO::WaitReadable exceptions' do + log2 = StringIO.new + config2 = SplitIoClient::SplitConfig.new(logger: Logger.new(log2)) + + mock_server do |server| + server.setup_response('/') do |_, res| + send_stream_content(res, event_occupancy) + end + start_workers + + sse_client2 = subject.new(config2, api_token, telemetry_runtime_producer, event_parser, notification_manager_keeper, notification_processor, push_status_queue) + + sse_client2.instance_variable_set(:@uri, URI(server.base_uri)) + latch = Concurrent::CountDownLatch.new(1) + + allow(sse_client2).to receive(:read_first_event).and_raise(IO::EWOULDBLOCKWaitReadable) + sleep(1) thr2 = Thread.new do - sse_client.send(:connect_stream, latch) + sse_client2.send(:connect_stream, latch) end - allow(sse_client).to receive(:read_first_event).and_return(true) - expect(thr2.status).to eq('run') + sleep(1) + allow(sse_client2).to receive(:read_first_event).and_return(true) + expect(log2.string).to include 'SSE client IO::WaitReadable transient error' stop_workers end From a3ca3ecd5f296cbb6175a7b58c6c04464b904cc2 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 13 Nov 2025 21:29:52 -0800 Subject: [PATCH 08/46] Fixed closing sslsocket --- lib/splitclient-rb/sse/event_source/client.rb | 69 ++++++++++--------- lib/splitclient-rb/version.rb | 2 +- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index 4cf498fe..ccc7cbf5 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -41,9 +41,12 @@ def close(status = nil) @config.logger.debug('SSEClient already disconected.') return end + @config.logger.debug("Closing SSEClient socket") @connected.make_false + @socket.sync_close = true @socket.close + @config.logger.debug("SSEClient socket state #{@socket.state}") push_status(status) rescue StandardError => e @config.logger.error("SSEClient close Error: #{e.inspect}") @@ -86,39 +89,43 @@ def connect_thread(latch) def connect_stream(latch) return Constants::PUSH_NONRETRYABLE_ERROR unless socket_write(latch) while connected? || @first_event.value - if IO.select([@socket], nil, nil, @read_timeout) - begin - partial_data = @socket.readpartial(10_000) - read_first_event(partial_data, latch) - - raise 'eof exception' if partial_data == :eof - rescue IO::WaitReadable => e - @config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") - IO.select([@socket], nil, nil, @read_timeout) - retry - rescue Errno::EAGAIN => e - @config.logger.debug("SSE client transient error: #{e.inspect}") - IO.select([@socket], nil, nil, @read_timeout) - retry - rescue Errno::ETIMEDOUT => e - @config.logger.error("SSE read operation timed out!: #{e.inspect}") - return Constants::PUSH_RETRYABLE_ERROR - rescue EOFError => e - @config.logger.error("SSE read operation EOF Exception!: #{e.inspect}") - raise 'eof exception' - rescue Errno::EBADF, IOError => e - @config.logger.error("SSE read operation EBADF or IOError: #{e.inspect}") - return Constants::PUSH_RETRYABLE_ERROR - rescue StandardError => e - @config.logger.error("SSE read operation StandardError: #{e.inspect}") - return nil if ENV['SPLITCLIENT_ENV'] == 'test' - - @config.logger.error("Error reading partial data: #{e.inspect}") + begin + if IO.select([@socket], nil, nil, @read_timeout) + begin + partial_data = @socket.readpartial(10_000) + read_first_event(partial_data, latch) + + raise 'eof exception' if partial_data == :eof + rescue IO::WaitReadable => e + @config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") + IO.select([@socket], nil, nil, @read_timeout) + retry + rescue Errno::EAGAIN => e + @config.logger.debug("SSE client transient error: #{e.inspect}") + IO.select([@socket], nil, nil, @read_timeout) + retry + rescue Errno::ETIMEDOUT => e + @config.logger.error("SSE read operation timed out!: #{e.inspect}") + return Constants::PUSH_RETRYABLE_ERROR + rescue EOFError => e + @config.logger.error("SSE read operation EOF Exception!: #{e.inspect}") + raise 'eof exception' + rescue Errno::EBADF, IOError => e + @config.logger.error("SSE read operation EBADF or IOError: #{e.inspect}") + return Constants::PUSH_RETRYABLE_ERROR + rescue StandardError => e + @config.logger.error("SSE read operation StandardError: #{e.inspect}") + return nil if ENV['SPLITCLIENT_ENV'] == 'test' + + @config.logger.error("Error reading partial data: #{e.inspect}") + return Constants::PUSH_RETRYABLE_ERROR + end + else + @config.logger.error("SSE read operation timed out, no data available.") return Constants::PUSH_RETRYABLE_ERROR end - else - @config.logger.error("SSE read operation timed out, no data available.") - return Constants::PUSH_RETRYABLE_ERROR + rescue Errno::EBADF + @config.logger.debug("SSE socket is not connected (Errno::EBADF)") end process_data(partial_data) diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index 015056f0..7fcee0f5 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.0-rc5' + VERSION = '8.10.0-rc7' end From 45cf15dea996e832dea5446c6b0bca94ab19b828 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 14 Nov 2025 09:37:01 -0800 Subject: [PATCH 09/46] polish --- lib/splitclient-rb/sse/event_source/client.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index ccc7cbf5..7dddfa73 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -126,6 +126,8 @@ def connect_stream(latch) end rescue Errno::EBADF @config.logger.debug("SSE socket is not connected (Errno::EBADF)") + finally + break end process_data(partial_data) From 3efb7b758c8def30c9e5c79995dae2a9c57316f5 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 14 Nov 2025 10:35:13 -0800 Subject: [PATCH 10/46] polish --- lib/splitclient-rb/sse/event_source/client.rb | 12 ++++++++---- spec/sse/sse_handler_spec.rb | 1 - 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index 7dddfa73..08ac28ee 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -44,9 +44,9 @@ def close(status = nil) @config.logger.debug("Closing SSEClient socket") @connected.make_false - @socket.sync_close = true + @socket.sync_close = true if @socket.is_a? OpenSSL::SSL::SSLSocket @socket.close - @config.logger.debug("SSEClient socket state #{@socket.state}") + @config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket push_status(status) rescue StandardError => e @config.logger.error("SSEClient close Error: #{e.inspect}") @@ -60,7 +60,6 @@ def start(url) @uri = URI(url) latch = Concurrent::CountDownLatch.new(1) - connect_thread(latch) return false unless latch.wait(CONNECT_TIMEOUT) @@ -108,6 +107,7 @@ def connect_stream(latch) @config.logger.error("SSE read operation timed out!: #{e.inspect}") return Constants::PUSH_RETRYABLE_ERROR rescue EOFError => e + puts "SSE read operation EOF Exception!: #{e.inspect}" @config.logger.error("SSE read operation EOF Exception!: #{e.inspect}") raise 'eof exception' rescue Errno::EBADF, IOError => e @@ -126,7 +126,11 @@ def connect_stream(latch) end rescue Errno::EBADF @config.logger.debug("SSE socket is not connected (Errno::EBADF)") - finally + break + rescue RuntimeError + raise 'eof exception' + rescue Exception => e + @config.logger.debug("SSE socket is not connected: #{e.inspect}") break end diff --git a/spec/sse/sse_handler_spec.rb b/spec/sse/sse_handler_spec.rb index 099f7932..96fb6141 100644 --- a/spec/sse/sse_handler_spec.rb +++ b/spec/sse/sse_handler_spec.rb @@ -48,7 +48,6 @@ config.streaming_service_url = server.base_uri sse_handler = subject.new(config, splits_worker, segments_worker, sse_client) - connected = sse_handler.start('token-test', 'channel-test') expect(connected).to eq(true) expect(sse_handler.connected?).to eq(true) From 6a8854343652c6b433bbe3aeefe4cecf2af09a8a Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 26 Nov 2025 08:18:20 -0800 Subject: [PATCH 11/46] updated changes and version --- CHANGES.txt | 3 +++ lib/splitclient-rb/version.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.txt b/CHANGES.txt index f899ea72..a9e4fc42 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,8 @@ CHANGES +8.10.0 (Nov 28, 2025) +- Replaced socketry gem used in streaming feature with built-in socket lib. + 8.9.0 (Oct 8, 2025) - Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index 7fcee0f5..d45f095e 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.0-rc7' + VERSION = '8.10.0' end From c3c10d5c5534cdfa0df81cab9e552e73c05dbf61 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 26 Nov 2025 09:31:41 -0800 Subject: [PATCH 12/46] upgraded java to 17 --- .github/workflows/ci.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1da52c42..8f166353 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -67,6 +67,12 @@ jobs: -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} -Dsonar.projectVersion=${{ env.VERSION }} + - name: Set up Java + uses: actions/setup-java@v2 + with: + java-version: 17 + distribution: "temurin" + - name: SonarQube Scan (Pull Request) if: matrix.version == '3.2.2' && github.event_name == 'pull_request' uses: SonarSource/sonarcloud-github-action@v1.9 From 0f079f203a217cf8e3749332961d70cafc70940a Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 26 Nov 2025 09:48:43 -0800 Subject: [PATCH 13/46] moved setup java to front --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f166353..bd9fb8d4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,12 @@ jobs: with: fetch-depth: 0 + - name: Set up Java + uses: actions/setup-java@v2 + with: + java-version: 17 + distribution: "temurin" + - name: Setup Ruby ${{ matrix.version }} uses: ruby/setup-ruby@v1 with: @@ -67,12 +73,6 @@ jobs: -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} -Dsonar.projectVersion=${{ env.VERSION }} - - name: Set up Java - uses: actions/setup-java@v2 - with: - java-version: 17 - distribution: "temurin" - - name: SonarQube Scan (Pull Request) if: matrix.version == '3.2.2' && github.event_name == 'pull_request' uses: SonarSource/sonarcloud-github-action@v1.9 From 5b030958cc71b0ed04eb50d778eac789cb008d63 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 26 Nov 2025 10:06:02 -0800 Subject: [PATCH 14/46] set hava to 17 for sonar --- sonar-project.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/sonar-project.properties b/sonar-project.properties index d98bd22f..ae9b0918 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,4 +2,5 @@ sonar.projectKey=ruby-client sonar.projectKey=ruby-client sonar.sources=lib sonar.tests=spec +sonar.java.source=17 sonar.ruby.coverage.reportPaths=coverage/.resultset.sonarqube.json From 7d422e66928ab8b61332f2e07ef6632d68e9db87 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 26 Nov 2025 11:19:39 -0800 Subject: [PATCH 15/46] upgraded sonar action --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd9fb8d4..8cdeaa0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -63,7 +63,7 @@ jobs: - name: SonarQube Scan (Push) if: matrix.version == '3.2.2' && github.event_name == 'push' - uses: SonarSource/sonarcloud-github-action@v1.9 + uses: SonarSource/sonarcloud-github-action@v5.0.0 env: SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -75,7 +75,7 @@ jobs: - name: SonarQube Scan (Pull Request) if: matrix.version == '3.2.2' && github.event_name == 'pull_request' - uses: SonarSource/sonarcloud-github-action@v1.9 + uses: SonarSource/sonarcloud-github-action@v5.0.0 env: SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 0727c0243bf138eff4fb6551bb1e7c03b40be5e2 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 26 Nov 2025 12:17:58 -0800 Subject: [PATCH 16/46] update sonar java --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8cdeaa0f..347d1b34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -82,6 +82,7 @@ jobs: with: projectBaseDir: . args: > + -Dsonar.java.source=17 -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} -Dsonar.projectVersion=${{ env.VERSION }} -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} From 08609b84104c1b08996930f36541dfc765bf57d0 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 26 Nov 2025 12:35:38 -0800 Subject: [PATCH 17/46] testing sonar --- .github/workflows/ci.yml | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 347d1b34..8981eb4b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,22 @@ jobs: - name: Install dependencies run: bundle install + - name: SonarQube Scan (Pull Request) + if: matrix.version == '3.2.2' && github.event_name == 'pull_request' + uses: SonarSource/sonarcloud-github-action@v5.0.0 + env: + SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + projectBaseDir: . + args: > + -Dsonar.java.source=17 + -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} + -Dsonar.projectVersion=${{ env.VERSION }} + -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} + -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} + -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} + - name: Run tests run: bundle exec rake @@ -73,18 +89,3 @@ jobs: -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} -Dsonar.projectVersion=${{ env.VERSION }} - - name: SonarQube Scan (Pull Request) - if: matrix.version == '3.2.2' && github.event_name == 'pull_request' - uses: SonarSource/sonarcloud-github-action@v5.0.0 - env: - SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - projectBaseDir: . - args: > - -Dsonar.java.source=17 - -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - -Dsonar.projectVersion=${{ env.VERSION }} - -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} - -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} - -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} From 1b0dab444f75f76311c5a64bbad0cf5de37a1e02 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 26 Nov 2025 12:37:14 -0800 Subject: [PATCH 18/46] revert --- .github/workflows/ci.yml | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8981eb4b..347d1b34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,22 +49,6 @@ jobs: - name: Install dependencies run: bundle install - - name: SonarQube Scan (Pull Request) - if: matrix.version == '3.2.2' && github.event_name == 'pull_request' - uses: SonarSource/sonarcloud-github-action@v5.0.0 - env: - SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - projectBaseDir: . - args: > - -Dsonar.java.source=17 - -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} - -Dsonar.projectVersion=${{ env.VERSION }} - -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} - -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} - -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} - - name: Run tests run: bundle exec rake @@ -89,3 +73,18 @@ jobs: -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} -Dsonar.projectVersion=${{ env.VERSION }} + - name: SonarQube Scan (Pull Request) + if: matrix.version == '3.2.2' && github.event_name == 'pull_request' + uses: SonarSource/sonarcloud-github-action@v5.0.0 + env: + SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + projectBaseDir: . + args: > + -Dsonar.java.source=17 + -Dsonar.host.url=${{ secrets.SONARQUBE_HOST }} + -Dsonar.projectVersion=${{ env.VERSION }} + -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} + -Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }} + -Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }} From d900bbb8c5fce0c80419cad9154dbd5de8bcb78d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 1 Jan 2026 03:30:26 +0000 Subject: [PATCH 19/46] Updated License Year --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index df08de3f..b6579621 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright © 2025 Split Software, Inc. +Copyright © 2026 Split Software, Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 4e72fb66e14c89f222ba9fc16ab18a1f3db21de7 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany <41021307+chillaq@users.noreply.github.com> Date: Fri, 9 Jan 2026 13:10:17 -0800 Subject: [PATCH 20/46] Update LICENSE --- LICENSE | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 169 insertions(+), 13 deletions(-) diff --git a/LICENSE b/LICENSE index b6579621..0f9e8a59 100644 --- a/LICENSE +++ b/LICENSE @@ -1,13 +1,169 @@ -Copyright © 2026 Split Software, Inc. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + 1. Definitions. + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + END OF TERMS AND CONDITIONS + APPENDIX: How to apply the Apache License to your work. + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + Copyright [yyyy] [name of copyright owner] + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 764f1ac4ccea2d847ec740de2c80a6631cc078d1 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 14 Jan 2026 19:07:28 -0800 Subject: [PATCH 21/46] fixed imp properties format in redis --- .../impressions/redis_repository.rb | 1 + lib/splitclient-rb/version.rb | 2 +- .../senders/impressions_formatter_spec.rb | 72 +++++++++++++------ spec/cache/senders/impressions_sender_spec.rb | 3 + 4 files changed, 54 insertions(+), 24 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb b/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb index 0a59c123..d9f8a098 100644 --- a/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +++ b/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb @@ -14,6 +14,7 @@ def initialize(config) def add_bulk(impressions) impressions_json = impressions.map do |impression| + impression[:i][:properties] = impression[:i][:properties].to_json.to_s unless impression[:i][:properties].nil? impression.to_json end diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index d45f095e..adf700f9 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.0' + VERSION = '8.10.1-rc1' end diff --git a/spec/cache/senders/impressions_formatter_spec.rb b/spec/cache/senders/impressions_formatter_spec.rb index 6c649a48..79b20c03 100644 --- a/spec/cache/senders/impressions_formatter_spec.rb +++ b/spec/cache/senders/impressions_formatter_spec.rb @@ -92,29 +92,55 @@ ] ) - expect(formatted_impressions.find { |i| i[:f] == :foo2 }[:i]).to match_array( - [ - { - k: 'matching_key2', - t: 'off', - m: 1_478_113_518_285, - b: 'foo2', - r: 'custom_label2', - c: 123_499, - pt: nil - }, - { - k: 'matching_key3', - t: 'off', - m: 1_478_113_518_900, - b: nil, - r: nil, - c: nil, - pt: nil, - properties: '{"prop":"val"}' - } - ] - ) + if cache_adapter == :redis + expect(formatted_impressions.find { |i| i[:f] == :foo2 }[:i]).to match_array( + [ + { + k: 'matching_key2', + t: 'off', + m: 1_478_113_518_285, + b: 'foo2', + r: 'custom_label2', + c: 123_499, + pt: nil + }, + { + k: 'matching_key3', + t: 'off', + m: 1_478_113_518_900, + b: nil, + r: nil, + c: nil, + pt: nil, + properties: '"{\"prop\":\"val\"}"' + } + ] + ) + else + expect(formatted_impressions.find { |i| i[:f] == :foo2 }[:i]).to match_array( + [ + { + k: 'matching_key2', + t: 'off', + m: 1_478_113_518_285, + b: 'foo2', + r: 'custom_label2', + c: 123_499, + pt: nil + }, + { + k: 'matching_key3', + t: 'off', + m: 1_478_113_518_900, + b: nil, + r: nil, + c: nil, + pt: nil, + properties: '{"prop":"val"}' + } + ] + ) + end end it 'filters out impressions with the same key/treatment' do diff --git a/spec/cache/senders/impressions_sender_spec.rb b/spec/cache/senders/impressions_sender_spec.rb index ccb5b885..3db870f7 100644 --- a/spec/cache/senders/impressions_sender_spec.rb +++ b/spec/cache/senders/impressions_sender_spec.rb @@ -55,6 +55,8 @@ end it 'post impressions with corresponding impressions metadata' do + skip "Test is not relevant for redis" if cache_adapter == :redis + stub_request(:post, 'https://events.split.io/api/testImpressions/bulk') .to_return(status: 200, body: 'ok') @@ -99,6 +101,7 @@ end it 'calls #post_impressions upon destroy' do + skip "Test is not relevant for redis" if cache_adapter == :redis stub_request(:post, 'https://events.split.io/api/testImpressions/bulk').to_return(status: 200, body: '') sender.call From 70869180f703bd0b2f6d317cd87e65bf3a05ad53 Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Wed, 21 Jan 2026 09:55:42 -0300 Subject: [PATCH 22/46] rbs matcher fix --- .../matchers/rule_based_segment_matcher.rb | 11 ++-- .../rule_based_segment_matcher_spec.rb | 57 ++++++++++++++++++- spec/integrations/in_memory_client_spec.rb | 4 +- .../rule_base_segments.json | 23 +++++++- 4 files changed, 87 insertions(+), 8 deletions(-) diff --git a/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb b/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb index a5463d0a..2443e258 100644 --- a/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +++ b/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb @@ -30,15 +30,18 @@ def match?(args) return false unless check_excluded_segments(rule_based_segment, key, args) - matches = false + matched = false rule_based_segment[:conditions].each do |c| condition = SplitIoClient::Condition.new(c, @config) next if condition.empty? - matches = Helpers::EvaluatorHelper.matcher_type(condition, @segments_repository, @rule_based_segments_repository).match?(args) + matched = Helpers::EvaluatorHelper.matcher_type(condition, @segments_repository, @rule_based_segments_repository).match?(args) + + break if matched end - @logger.debug("[InRuleSegmentMatcher] #{@segment_name} is in rule based segment -> #{matches}") - matches + + @logger.debug("[InRuleSegmentMatcher] #{@segment_name} is in rule based segment -> #{matched}") + matched end private diff --git a/spec/engine/matchers/rule_based_segment_matcher_spec.rb b/spec/engine/matchers/rule_based_segment_matcher_spec.rb index 4f7786e4..6034dfc3 100644 --- a/spec/engine/matchers/rule_based_segment_matcher_spec.rb +++ b/spec/engine/matchers/rule_based_segment_matcher_spec.rb @@ -177,6 +177,61 @@ matcher = described_class.new(segments_repository, rbs_repositoy, 'sample_rule_based_segment', config) expect(matcher.match?(value: 'bilal@split.io', attributes: {'email': 'bilal@split.io'})).to be true expect(matcher.match?(value: 'bilal', attributes: {'email': 'bilal'})).to be false - end + end + + it 'return true if has multiple conditions' do + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs = { + :name => 'sample_rule_based_segment', + :trafficTypeName => 'tt_name_1', + :conditions => [ + { + :matcherGroup => { + :combiner => "AND", + :matchers => [ + { + :matcherType => "WHITELIST", + :negate => false, + :userDefinedSegmentMatcherData => nil, + :whitelistMatcherData => { + :whitelist => [ + "bilal" + ] + }, + :unaryNumericMatcherData => nil, + :betweenMatcherData => nil + } + ] + } + }, + { + :matcherGroup => { + :combiner => "AND", + :matchers => [ + { + :matcherType => "WHITELIST", + :negate => false, + :userDefinedSegmentMatcherData => nil, + :whitelistMatcherData => { + :whitelist => [ + "mauro" + ] + }, + :unaryNumericMatcherData => nil, + :betweenMatcherData => nil + } + ] + } + } + ], + :excluded => {:keys => [], :segments => []} + } + + rbs_repositoy.update([rbs], [], -1) + matcher = described_class.new(segments_repository, rbs_repositoy, 'sample_rule_based_segment', config) + expect(matcher.match?(value: 'mauro', attributes: {})).to be true + expect(matcher.match?(value: 'bilal', attributes: {})).to be true + expect(matcher.match?(value: 'nicolas', attributes: {})).to be false + end end end \ No newline at end of file diff --git a/spec/integrations/in_memory_client_spec.rb b/spec/integrations/in_memory_client_spec.rb index 6802710d..b65a8fd5 100644 --- a/spec/integrations/in_memory_client_spec.rb +++ b/spec/integrations/in_memory_client_spec.rb @@ -1401,8 +1401,8 @@ client_rbs = factory_rbs.client client_rbs.block_until_ready - expect(client_rbs.get_treatment('bilal@split.io', 'rbs_feature_flag', {:email => 'bilal@split.io'})).to eq('on') - expect(client_rbs.get_treatment('mauro@split.io', 'rbs_feature_flag', {:email => 'mauro@split.io'})).to eq('off') + expect(client_rbs.get_treatment('bilal', 'rbs_feature_flag', {:email => 'bilal@split.io'})).to eq('on') + expect(client_rbs.get_treatment('mauro', 'rbs_feature_flag', {:email => 'mauro@split.io'})).to eq('off') end end diff --git a/spec/test_data/rule_based_segments/rule_base_segments.json b/spec/test_data/rule_based_segments/rule_base_segments.json index deafbdc2..6a9b4bd4 100644 --- a/spec/test_data/rule_based_segments/rule_base_segments.json +++ b/spec/test_data/rule_based_segments/rule_base_segments.json @@ -78,7 +78,7 @@ "name": "dependent_rbs", "status": "ACTIVE", "trafficTypeName": "user", - "excluded":{"keys":["mauro@split.io","gaston@split.io"],"segments":[]}, + "excluded":{"keys":["mauro","gaston@split.io"],"segments":[]}, "conditions": [ { "conditionType": "WHITELIST", @@ -100,6 +100,27 @@ } ] } + }, + { + "conditionType": "WHITELIST", + "matcherGroup": { + "combiner": "AND", + "matchers": [ + { + "keySelector": { + "trafficType": "user", + "attribute": "email" + }, + "matcherType": "ENDS_WITH", + "negate": false, + "whitelistMatcherData": { + "whitelist": [ + "@harness.io" + ] + } + } + ] + } } ]}, { From f90f6bec8b27c6d11961acbb5c2c26de114e20f5 Mon Sep 17 00:00:00 2001 From: Mauro Antonio Sanz Date: Wed, 21 Jan 2026 09:58:59 -0300 Subject: [PATCH 23/46] update changes --- CHANGES.txt | 5 ++++- lib/splitclient-rb/version.rb | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index a9e4fc42..7dc8f06f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,10 @@ CHANGES +8.10.1 (Jan 21, 2025) +- Fixed rule-based segment matcher. + 8.10.0 (Nov 28, 2025) -- Replaced socketry gem used in streaming feature with built-in socket lib. +- Updated socketry gem used in streaming feature with built-in socket lib. 8.9.0 (Oct 8, 2025) - Added new configuration for Fallback Treatments, which allows setting a treatment value and optional config to be returned in place of "control", either globally or by flag. Read more in our docs. diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index adf700f9..6a908fc6 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.1-rc1' + VERSION = '8.10.1-rc.2' end From 30369f7ed35e318f923cde42abf570fed659ca90 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 27 Jan 2026 09:57:06 -0800 Subject: [PATCH 24/46] updated version and changes --- CHANGES.txt | 5 +++-- lib/splitclient-rb/version.rb | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index 7dc8f06f..ecfe21a6 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,7 +1,8 @@ CHANGES -8.10.1 (Jan 21, 2025) -- Fixed rule-based segment matcher. +8.10.1 (Jan 28, 2025) +- Fixed rule-based segment matcher to exit when a conition is met. +- Fixed impressions properties format in redis mode. 8.10.0 (Nov 28, 2025) - Updated socketry gem used in streaming feature with built-in socket lib. diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index 6a908fc6..82ab6583 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.1-rc.2' + VERSION = '8.10.1' end From 829d5425b6c84f696db7ec9d52e1d1cf8eaa1ea3 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 27 Jan 2026 12:19:52 -0800 Subject: [PATCH 25/46] moved pushing status upward to fix tests --- lib/splitclient-rb/sse/event_source/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index 08ac28ee..bb07d76a 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -43,11 +43,11 @@ def close(status = nil) end @config.logger.debug("Closing SSEClient socket") + push_status(status) @connected.make_false @socket.sync_close = true if @socket.is_a? OpenSSL::SSL::SSLSocket @socket.close @config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket - push_status(status) rescue StandardError => e @config.logger.error("SSEClient close Error: #{e.inspect}") end From fdfb21b6094131fccf4139f1eae9c8a7ad24d350 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 27 Jan 2026 14:53:58 -0800 Subject: [PATCH 26/46] added notice file --- NOTICE.txt | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 NOTICE.txt diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 00000000..7d7d845e --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,5 @@ +Harness Feature Management JavaScript SDK Copyright 2024-2026 Harness Inc. + +This product includes software developed at Harness Inc. (https://harness.io/). + +This product includes software originally developed by Split Software, Inc. (https://www.split.io/). Copyright 2015-2024 Split Software, Inc. From 0e7ce0a9bb446f4a4fe5d734c9b4d68dd7d0b433 Mon Sep 17 00:00:00 2001 From: Mauro Sanz <51236193+sanzmauro@users.noreply.github.com> Date: Tue, 27 Jan 2026 19:57:13 -0300 Subject: [PATCH 27/46] Update NOTICE.txt --- NOTICE.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NOTICE.txt b/NOTICE.txt index 7d7d845e..586e4ca5 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -2,4 +2,4 @@ Harness Feature Management JavaScript SDK Copyright 2024-2026 Harness Inc. This product includes software developed at Harness Inc. (https://harness.io/). -This product includes software originally developed by Split Software, Inc. (https://www.split.io/). Copyright 2015-2024 Split Software, Inc. +This product includes software originally developed by Split Software, Inc. (https://www.split.io/). Copyright 2016-2024 Split Software, Inc. From 1c58b6ccedb82204cacce7afc36aecf45a8408fe Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 9 Feb 2026 10:42:24 -0800 Subject: [PATCH 28/46] added models classes --- lib/splitclient-rb.rb | 5 ++ .../engine/events/events_manager_config.rb | 87 +++++++++++++++++++ .../engine/models/events_metadata.rb | 10 +++ lib/splitclient-rb/engine/models/sdk_event.rb | 4 + .../engine/models/sdk_event_type.rb | 4 + .../engine/models/sdk_internal_event.rb | 8 ++ .../events/events_manager_config_spec.rb | 37 ++++++++ 7 files changed, 155 insertions(+) create mode 100644 lib/splitclient-rb/engine/events/events_manager_config.rb create mode 100644 lib/splitclient-rb/engine/models/events_metadata.rb create mode 100644 lib/splitclient-rb/engine/models/sdk_event.rb create mode 100644 lib/splitclient-rb/engine/models/sdk_event_type.rb create mode 100644 lib/splitclient-rb/engine/models/sdk_internal_event.rb create mode 100644 spec/engine/events/events_manager_config_spec.rb diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index 5f4ebcc9..e30bfed0 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -66,6 +66,7 @@ require 'splitclient-rb/engine/common/impressions_counter' require 'splitclient-rb/engine/common/impressions_manager' require 'splitclient-rb/engine/common/noop_impressions_counter' +require 'splitclient-rb/engine/events/events_manager_config.rb' require 'splitclient-rb/engine/parser/condition' require 'splitclient-rb/engine/parser/partition' require 'splitclient-rb/engine/parser/evaluator' @@ -112,6 +113,10 @@ require 'splitclient-rb/engine/models/evaluation_options' require 'splitclient-rb/engine/models/fallback_treatment.rb' require 'splitclient-rb/engine/models/fallback_treatments_configuration.rb' +require 'splitclient-rb/engine/models/events_metadata.rb' +require 'splitclient-rb/engine/models/sdk_event_type.rb' +require 'splitclient-rb/engine/models/sdk_event.rb' +require 'splitclient-rb/engine/models/sdk_internal_event.rb' require 'splitclient-rb/engine/auth_api_client' require 'splitclient-rb/engine/back_off' require 'splitclient-rb/engine/fallback_treatment_calculator.rb' diff --git a/lib/splitclient-rb/engine/events/events_manager_config.rb b/lib/splitclient-rb/engine/events/events_manager_config.rb new file mode 100644 index 00000000..03577af9 --- /dev/null +++ b/lib/splitclient-rb/engine/events/events_manager_config.rb @@ -0,0 +1,87 @@ +# frozen_string_literal: true + +module SplitIoClient::Engine::Events + class EventsManagerConfig + attr_accessor :require_all, :prerequisites, :require_any, :suppressed_by, :execution_limits, :evaluation_order + + def initialize + @require_all = get_require_all + @prerequisites = get_prerequisites + @require_any = get_require_any + @suppressed_by = get_suppressed_by + @execution_limits = get_execution_limits + @evaluation_order = get_sorted_events + end + + private + + def get_require_all + return { + SplitIoClient::Engine::Models::SdkEvent::SDK_READY => Set.new([SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY]) + } + end + + def get_prerequisites + return { + SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new([SplitIoClient::Engine::Models::SdkEvent::SDK_READY]) + } + end + + def get_require_any + return { + SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new([SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION, SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED, SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED]) + } + end + + def get_suppressed_by + return {} + end + + def get_execution_limits + return { + SplitIoClient::Engine::Models::SdkEvent::SDK_READY => 1, + SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => -1 + } + end + + def get_sorted_events + sorted_events = [] + for sdk_event in [SplitIoClient::Engine::Models::SdkEvent::SDK_READY, SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE] + sorted_events = dfs_recursive(sdk_event, sorted_events) + end + + return sorted_events + end + + def dfs_recursive(sdk_event, added) + return added if added.include?(sdk_event) + + get_dependencies(sdk_event).each do |dependent_event| + added = dfs_recursive(dependent_event, added) + end + + added.push(sdk_event) + return added + end + + def get_dependencies(sdk_event) + dependencies = Set.new + @prerequisites.each do |prerequisites_event_name, prerequisites_event_value| + if prerequisites_event_name == sdk_event + for prereq_event in prerequisites_event_value + dependencies.add(prereq_event) + end + end + end + + @suppressed_by.each do |suppressed_event_name, suppressed_event_value| + if suppressed_event_value.include?(sdk_event) + dependencies.add(suppressed_event_name) + end + end + + return dependencies + end + end +end \ No newline at end of file diff --git a/lib/splitclient-rb/engine/models/events_metadata.rb b/lib/splitclient-rb/engine/models/events_metadata.rb new file mode 100644 index 00000000..208c24b5 --- /dev/null +++ b/lib/splitclient-rb/engine/models/events_metadata.rb @@ -0,0 +1,10 @@ +module SplitIoClient::Engine::Models + class EventsMetadata + attr_accessor :type, :names + + def initialize(type, names=nil) + @type = type + @names = names + end + end +end diff --git a/lib/splitclient-rb/engine/models/sdk_event.rb b/lib/splitclient-rb/engine/models/sdk_event.rb new file mode 100644 index 00000000..4d9fa189 --- /dev/null +++ b/lib/splitclient-rb/engine/models/sdk_event.rb @@ -0,0 +1,4 @@ +class SplitIoClient::Engine::Models::SdkEvent + SDK_READY = 'SDK_READY' + SDK_UPDATE = 'SDK_UPDATE' +end diff --git a/lib/splitclient-rb/engine/models/sdk_event_type.rb b/lib/splitclient-rb/engine/models/sdk_event_type.rb new file mode 100644 index 00000000..a8a8e0fc --- /dev/null +++ b/lib/splitclient-rb/engine/models/sdk_event_type.rb @@ -0,0 +1,4 @@ +class SplitIoClient::Engine::Models::SdkEventType + FLAG_UPDATE = 'FLAG_UPDATE' + SEGMENTS_UPDATE = 'SEGMENTS_UPDATE' +end diff --git a/lib/splitclient-rb/engine/models/sdk_internal_event.rb b/lib/splitclient-rb/engine/models/sdk_internal_event.rb new file mode 100644 index 00000000..8372a132 --- /dev/null +++ b/lib/splitclient-rb/engine/models/sdk_internal_event.rb @@ -0,0 +1,8 @@ +class SplitIoClient::Engine::Models::SdkInternalEvent + SDK_READY = 'SDK_READY' + FLAGS_UPDATED = 'FLAGS_UPDATED' + FLAG_KILLED_NOTIFICATION = 'FLAG_KILLED_NOTIFICATION' + SEGMENTS_UPDATED = 'SEGMENTS_UPDATED' + RB_SEGMENTS_UPDATED = 'RB_SEGMENTS_UPDATED' + LARGE_SEGMENTS_UPDATED = 'LARGE_SEGMENTS_UPDATED' +end diff --git a/spec/engine/events/events_manager_config_spec.rb b/spec/engine/events/events_manager_config_spec.rb new file mode 100644 index 00000000..46c0a5c3 --- /dev/null +++ b/spec/engine/events/events_manager_config_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::Engine::Events::EventsManagerConfig do + subject { SplitIoClient::Engine::Events::EventsManagerConfig } + + it 'test_build_instance' do + config = subject.new + + expect(config.require_all[SplitIoClient::Engine::Models::SdkEvent::SDK_READY].length).to eq(1) + expect(config.require_all[SplitIoClient::Engine::Models::SdkEvent::SDK_READY].include?(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY)).to eq(true) + + expect(config.prerequisites[SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].include?(SplitIoClient::Engine::Models::SdkEvent::SDK_READY)).to eq(true) + + expect(config.execution_limits[SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE]).to eq(-1) + expect(config.execution_limits[SplitIoClient::Engine::Models::SdkEvent::SDK_READY]).to eq(1) + + expect(config.require_any[SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].length).to eq(4) + expect(config.require_any[SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].include?(SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION)).to be(true) + expect(config.require_any[SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].include?(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED)).to be(true) + expect(config.require_any[SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].include?(SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED)).to be(true) + expect(config.require_any[SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].include?(SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED)).to be(true) + + order = 0 + expect(config.evaluation_order.length).to eq(2) + config.evaluation_order.each do |sdk_event| + order += 1 + if order == 1 + expect(sdk_event).to eq(SplitIoClient::Engine::Models::SdkEvent::SDK_READY) + end + if order == 2 + expect(sdk_event).to eq(SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE) + end + end + end +end From 178bcf4592690bb1b87cbfa5fe3b25b8a78a32db Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 13 Feb 2026 14:53:59 -0800 Subject: [PATCH 29/46] added events task and polishing. --- lib/splitclient-rb.rb | 2 + .../engine/events/events_manager_config.rb | 119 ++++++++++-------- .../engine/events/events_task.rb | 50 ++++++++ .../models/sdk_internal_event_notification.rb | 14 +++ spec/engine/events/events_task_spec.rb | 40 ++++++ 5 files changed, 170 insertions(+), 55 deletions(-) create mode 100644 lib/splitclient-rb/engine/events/events_task.rb create mode 100644 lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb create mode 100644 spec/engine/events/events_task_spec.rb diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index e30bfed0..67688324 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -67,6 +67,7 @@ require 'splitclient-rb/engine/common/impressions_manager' require 'splitclient-rb/engine/common/noop_impressions_counter' require 'splitclient-rb/engine/events/events_manager_config.rb' +require 'splitclient-rb/engine/events/events_task.rb' require 'splitclient-rb/engine/parser/condition' require 'splitclient-rb/engine/parser/partition' require 'splitclient-rb/engine/parser/evaluator' @@ -117,6 +118,7 @@ require 'splitclient-rb/engine/models/sdk_event_type.rb' require 'splitclient-rb/engine/models/sdk_event.rb' require 'splitclient-rb/engine/models/sdk_internal_event.rb' +require 'splitclient-rb/engine/models/sdk_internal_event_notification.rb' require 'splitclient-rb/engine/auth_api_client' require 'splitclient-rb/engine/back_off' require 'splitclient-rb/engine/fallback_treatment_calculator.rb' diff --git a/lib/splitclient-rb/engine/events/events_manager_config.rb b/lib/splitclient-rb/engine/events/events_manager_config.rb index 03577af9..051f5879 100644 --- a/lib/splitclient-rb/engine/events/events_manager_config.rb +++ b/lib/splitclient-rb/engine/events/events_manager_config.rb @@ -1,87 +1,96 @@ # frozen_string_literal: true -module SplitIoClient::Engine::Events - class EventsManagerConfig +module SplitIoClient + module Engine + module Events + class EventsManagerConfig attr_accessor :require_all, :prerequisites, :require_any, :suppressed_by, :execution_limits, :evaluation_order def initialize - @require_all = get_require_all - @prerequisites = get_prerequisites - @require_any = get_require_any - @suppressed_by = get_suppressed_by - @execution_limits = get_execution_limits - @evaluation_order = get_sorted_events + @require_all = construct_require_all + @prerequisites = construct_prerequisites + @require_any = construct_require_any + @suppressed_by = construct_suppressed_by + @execution_limits = construct_execution_limits + @evaluation_order = construct_sorted_events end - + private - def get_require_all - return { - SplitIoClient::Engine::Models::SdkEvent::SDK_READY => Set.new([SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY]) - } + def construct_require_all + { + SplitIoClient::Engine::Models::SdkEvent::SDK_READY => Set.new([SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY]) + } end - def get_prerequisites - return { - SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new([SplitIoClient::Engine::Models::SdkEvent::SDK_READY]) - } + def construct_prerequisites + { + SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new([SplitIoClient::Engine::Models::SdkEvent::SDK_READY]) + } end - def get_require_any - return { - SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new([SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION, SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, - SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED, SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED]) - } + def construct_require_any + { + SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new( + [ + SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION, + SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED, + SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED + ] + ) + } end - def get_suppressed_by - return {} + def construct_suppressed_by + {} end - def get_execution_limits - return { - SplitIoClient::Engine::Models::SdkEvent::SDK_READY => 1, - SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => -1 - } + def construct_execution_limits + { + SplitIoClient::Engine::Models::SdkEvent::SDK_READY => 1, + SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => -1 + } end - def get_sorted_events - sorted_events = [] - for sdk_event in [SplitIoClient::Engine::Models::SdkEvent::SDK_READY, SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE] - sorted_events = dfs_recursive(sdk_event, sorted_events) - end + def construct_sorted_events + sorted_events = [] + [SplitIoClient::Engine::Models::SdkEvent::SDK_READY, SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].each do |sdk_event| + sorted_events = dfs_recursive(sdk_event, sorted_events) + end - return sorted_events + sorted_events end def dfs_recursive(sdk_event, added) - return added if added.include?(sdk_event) + return added if added.include?(sdk_event) - get_dependencies(sdk_event).each do |dependent_event| - added = dfs_recursive(dependent_event, added) - end + get_dependencies(sdk_event).each do |dependent_event| + added = dfs_recursive(dependent_event, added) + end - added.push(sdk_event) - return added + added.push(sdk_event) + + added end def get_dependencies(sdk_event) - dependencies = Set.new - @prerequisites.each do |prerequisites_event_name, prerequisites_event_value| - if prerequisites_event_name == sdk_event - for prereq_event in prerequisites_event_value - dependencies.add(prereq_event) - end - end - end + dependencies = Set.new + @prerequisites.each do |prerequisites_event_name, prerequisites_event_value| + skip unless prerequisites_event_name == sdk_event - @suppressed_by.each do |suppressed_event_name, suppressed_event_value| - if suppressed_event_value.include?(sdk_event) - dependencies.add(suppressed_event_name) - end + prerequisites_event_value.each do |prereq_event| + dependencies.add(prereq_event) end + end + + @suppressed_by.each do |suppressed_event_name, suppressed_event_value| + dependencies.add(suppressed_event_name) if suppressed_event_value.include?(sdk_event) + end - return dependencies + dependencies end + end end -end \ No newline at end of file + end +end diff --git a/lib/splitclient-rb/engine/events/events_task.rb b/lib/splitclient-rb/engine/events/events_task.rb new file mode 100644 index 00000000..42bc736b --- /dev/null +++ b/lib/splitclient-rb/engine/events/events_task.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +module SplitIoClient + module Engine + module Events + class EventsTask + attr_accessor :running + + def initialize(notify_internal_events, internal_events_queue, config) + @notify_internal_events = notify_internal_events + @internal_events_queue = internal_events_queue + @config = config + @running = false + end + + def start + return if @running + + @config.logger.info('Starting Internal Events Task.') if @config.debug_enabled + @running = true + @config.threads[:internal_events_task] = Thread.new do + worker_thread + end + end + + def stop + return unless @running + + @config.logger.info('Stopping Internal Events Task.') if @config.debug_enabled + @running = false + end + + private + + def worker_thread + while (event = @internal_events_queue.pop) + break unless @running + + @config.logger.info("Processing sdk internal event: #{event.internal_event}") if @config.debug_enabled + begin + @notify_internal_events.call(event.internal_event, event.metadata) + rescue StandardError => e + @config.log_found_exception(__method__.to_s, e) + end + end + end + end + end + end +end diff --git a/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb b/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb new file mode 100644 index 00000000..e202c707 --- /dev/null +++ b/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: false + +module SplitIoClient + module Engine::Models + class SdkInternalEventNotification + attr_reader :internal_event, :metadata + + def initialize(internal_event, metadata) + @internal_event = internal_event + @metadata = metadata + end + end + end +end diff --git a/spec/engine/events/events_task_spec.rb b/spec/engine/events/events_task_spec.rb new file mode 100644 index 00000000..b9a20efe --- /dev/null +++ b/spec/engine/events/events_task_spec.rb @@ -0,0 +1,40 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::Engine::Events::EventsTask do + subject { SplitIoClient::Engine::Events::EventsTask } + let(:internal_event) { nil } + let(:metadata) { nil } + + it 'test_task_running' do + queue = Queue.new + config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) + task = subject.new(method(:call_back), queue, config) + task.start + expect(task.running).to be(true) + + queue.push(SplitIoClient::Engine::Models::SdkInternalEventNotification.new(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE))) + sleep 0.5 + expect(@internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED) + expect(@metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE) + + @internal_event = nil + @metadata = nil + queue.push(SplitIoClient::Engine::Models::SdkInternalEventNotification.new(SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED, SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE))) + sleep 0.5 + expect(@internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED) + expect(@metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE) + + task.stop + sleep 0.2 + expect(task.running).to be(false) + + end + + def call_back(internal_event, metadata) + @internal_event = internal_event + @metadata = metadata + end + +end From 6de8c7cd5c0b5378784ffacd9fd6ae0833a02943 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 13 Feb 2026 14:55:59 -0800 Subject: [PATCH 30/46] fixed loop --- lib/splitclient-rb/engine/events/events_manager_config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/splitclient-rb/engine/events/events_manager_config.rb b/lib/splitclient-rb/engine/events/events_manager_config.rb index 051f5879..4fe4b226 100644 --- a/lib/splitclient-rb/engine/events/events_manager_config.rb +++ b/lib/splitclient-rb/engine/events/events_manager_config.rb @@ -77,7 +77,7 @@ def dfs_recursive(sdk_event, added) def get_dependencies(sdk_event) dependencies = Set.new @prerequisites.each do |prerequisites_event_name, prerequisites_event_value| - skip unless prerequisites_event_name == sdk_event + next unless prerequisites_event_name == sdk_event prerequisites_event_value.each do |prereq_event| dependencies.add(prereq_event) From d6201100a60b12e79a3b365725774fa7e363102e Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Sun, 15 Feb 2026 16:54:04 -0300 Subject: [PATCH 31/46] updated split, segment and rbsegment repositories --- .../rule_based_segments_repository.rb | 15 +++++++- .../cache/repositories/segments_repository.rb | 12 ++++++- .../cache/repositories/splits_repository.rb | 24 ++++++++++++- .../rule_based_segments_repository_spec.rb | 30 +++++++++++++++- .../repositories/segments_repository_spec.rb | 18 ++++++++-- .../repositories/splits_repository_spec.rb | 36 ++++++++++++++++++- 6 files changed, 127 insertions(+), 8 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb b/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb index 7f45e343..67f339fe 100644 --- a/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +++ b/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb @@ -28,7 +28,7 @@ class RuleBasedSegmentsRepository < Repository RB_SEGMENTS_PREFIX = '.rbsegment.' REGISTERED_PREFIX = '.segments.registered' - def initialize(config) + def initialize(config, internal_events_queue) super(config) @adapter = case @config.cache_adapter.class.to_s when 'SplitIoClient::Cache::Adapters::RedisAdapter' @@ -40,12 +40,25 @@ def initialize(config) @adapter.set_string(namespace_key(TILL_PREFIX), '-1') @adapter.initialize_map(namespace_key(REGISTERED_PREFIX)) end + @internal_events_queue = internal_events_queue end def update(to_add, to_delete, new_change_number) to_add.each{ |rule_based_segment| add_rule_based_segment(rule_based_segment) } to_delete.each{ |rule_based_segment| remove_rule_based_segment(rule_based_segment) } set_change_number(new_change_number) + + if to_add.length > 0 || to_delete.length > 0 + @internal_events_queue.push( + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE, + [] + ) + ) + ) + end end def get_rule_based_segment(name) diff --git a/lib/splitclient-rb/cache/repositories/segments_repository.rb b/lib/splitclient-rb/cache/repositories/segments_repository.rb index a45ee167..900da206 100644 --- a/lib/splitclient-rb/cache/repositories/segments_repository.rb +++ b/lib/splitclient-rb/cache/repositories/segments_repository.rb @@ -6,7 +6,7 @@ class SegmentsRepository < Repository attr_reader :adapter - def initialize(config) + def initialize(config, internal_events_queue) super(config) @adapter = case @config.cache_adapter.class.to_s when 'SplitIoClient::Cache::Adapters::RedisAdapter' @@ -15,6 +15,7 @@ def initialize(config) @config.cache_adapter end @adapter.set_bool(namespace_key('.ready'), false) unless @config.mode.equal?(:consumer) + @internal_events_queue = internal_events_queue end # Receives segment data, adds and removes segements from the store @@ -25,6 +26,15 @@ def add_to_segment(segment) add_keys(name, segment[:added]) remove_keys(name, segment[:removed]) + @internal_events_queue.push( + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE, + [] + ) + ) + ) end def get_segment_keys(name) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 9fcae0e0..f9a8acbb 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -35,7 +35,7 @@ class SplitsRepository < Repository SPLIT_PREFIX = '.split.' READY_PREFIX = '.splits.ready' - def initialize(config, flag_sets_repository, flag_set_filter) + def initialize(config, flag_sets_repository, flag_set_filter, internal_events_queue) super(config) @tt_cache = {} @adapter = case @config.cache_adapter.class.to_s @@ -46,6 +46,7 @@ def initialize(config, flag_sets_repository, flag_set_filter) end @flag_sets = flag_sets_repository @flag_set_filter = flag_set_filter + @internal_events_queue = internal_events_queue initialize_keys end @@ -53,6 +54,18 @@ def update(to_add, to_delete, new_change_number) to_add.each{ |feature_flag| add_feature_flag(feature_flag) } to_delete.each{ |feature_flag| remove_feature_flag(feature_flag) } set_change_number(new_change_number) + + if to_add.length > 0 || to_delete.length > 0 + @internal_events_queue.push( + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, + to_add.map {|flag| flag[:name]} | to_delete.map {|flag| flag[:name]} + ) + ) + ) + end end def get_split(name) @@ -140,6 +153,15 @@ def kill(change_number, split_name, default_treatment) split[:changeNumber] = change_number @adapter.set_string(namespace_key(".split.#{split_name}"), split.to_json) + @internal_events_queue.push( + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, + [split_name] + ) + ) + ) end def splits_count diff --git a/spec/cache/repositories/rule_based_segments_repository_spec.rb b/spec/cache/repositories/rule_based_segments_repository_spec.rb index 3fdd89ed..f583105e 100644 --- a/spec/cache/repositories/rule_based_segments_repository_spec.rb +++ b/spec/cache/repositories/rule_based_segments_repository_spec.rb @@ -6,7 +6,8 @@ describe SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository do RSpec.shared_examples 'RuleBasedSegments Repository' do |cache_adapter| let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: cache_adapter) } - let(:repository) { described_class.new(config) } + let(:queue) {Queue.new} + let(:repository) { described_class.new(config, queue) } before :all do redis = Redis.new @@ -102,6 +103,33 @@ repository.update([rule_based_segment2], [], -1) expect(repository.get_rule_based_segment('corge2')[:conditions]).to eq SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository::DEFAULT_CONDITIONS_TEMPLATE end + + it 'push to internal event queue' do + queue.clear() + + repository.update([{name: 'flag1', trafficTypeName: 'tt_name_1', conditions: []}, + {name: 'flag2', trafficTypeName: 'tt_name_1', conditions: []}], [], -1) + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED) + expect(event.metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE) + expect(event.metadata.names).to eq([]) + + repository.update([{name: 'flag1', trafficTypeName: 'tt_name_1', killed: false, default_treatment: 'on', changeNumber: 1, conditions: []}], + [{name: 'flag2', trafficTypeName: 'tt_name_1', conditions: []}], -1) + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED) + expect(event.metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE) + expect(event.metadata.names).to eq([]) + + repository.update([], [], 123) + expect(queue.empty?).to eq(true) + + repository.update([], [{name: 'flag1', trafficTypeName: 'tt_name_1', killed: false, default_treatment: 'on', changeNumber: 1, conditions: []}], -1) + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED) + expect(event.metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE) + expect(event.metadata.names).to eq([]) + end end describe 'with Memory Adapter' do diff --git a/spec/cache/repositories/segments_repository_spec.rb b/spec/cache/repositories/segments_repository_spec.rb index cd2dd021..3cfac1ae 100644 --- a/spec/cache/repositories/segments_repository_spec.rb +++ b/spec/cache/repositories/segments_repository_spec.rb @@ -4,10 +4,11 @@ describe SplitIoClient::Cache::Repositories::SegmentsRepository do context 'memory adapter' do - let(:repository) { described_class.new(@default_config) } + let(:queue) {Queue.new} + let(:repository) { described_class.new(@default_config, queue) } let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) } let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) } - let(:split_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(@default_config, flag_sets_repository, flag_set_filter) } + let(:split_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(@default_config, flag_sets_repository, flag_set_filter, queue) } it 'removes keys' do repository.add_to_segment(name: 'foo', added: [1, 2, 3], removed: []) @@ -27,10 +28,21 @@ expect(repository.segments_count).to be(3) expect(repository.segment_keys_count).to be(7) end + + it 'push to internal event queue' do + queue.clear() + + repository.add_to_segment(name: 'foo', added: [1, 2, 3], removed: []) + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED) + expect(event.metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE) + expect(event.metadata.names).to eq([]) + end end context 'redis adapter' do - let(:repository) { described_class.new(SplitIoClient::SplitConfig.new(cache_adapter: :redis)) } + let(:queue) {Queue.new} + let(:repository) { described_class.new(SplitIoClient::SplitConfig.new(cache_adapter: :redis), queue) } it 'removes keys' do repository.add_to_segment(name: 'foo', added: [1, 2, 3], removed: []) diff --git a/spec/cache/repositories/splits_repository_spec.rb b/spec/cache/repositories/splits_repository_spec.rb index b55bfd11..ab18b84d 100644 --- a/spec/cache/repositories/splits_repository_spec.rb +++ b/spec/cache/repositories/splits_repository_spec.rb @@ -8,7 +8,8 @@ let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: cache_adapter) } let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(config)} let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} - let(:repository) { described_class.new(config, flag_sets_repository, flag_set_filter) } + let(:queue) {Queue.new} + let(:repository) { described_class.new(config, flag_sets_repository, flag_set_filter, queue) } before :all do redis = Redis.new @@ -172,6 +173,39 @@ repository.update([split2], [], -1) expect(repository.get_split('corge2')[:conditions]).to eq SplitIoClient::Cache::Repositories::SplitsRepository::DEFAULT_CONDITIONS_TEMPLATE end + + it 'push to internal event queue' do + queue.clear() + + repository.update([{name: 'flag1', trafficTypeName: 'tt_name_1', conditions: []}, + {name: 'flag2', trafficTypeName: 'tt_name_1', conditions: []}], [], -1) + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED) + expect(event.metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE) + expect(event.metadata.names).to eq(['flag1', 'flag2']) + + repository.update([{name: 'flag1', trafficTypeName: 'tt_name_1', killed: false, default_treatment: 'on', changeNumber: 1, conditions: []}], + [{name: 'flag2', trafficTypeName: 'tt_name_1', conditions: []}], -1) + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED) + expect(event.metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE) + expect(event.metadata.names).to eq(['flag1', 'flag2']) + + repository.update([], [], 123) + expect(queue.empty?).to eq(true) + + repository.kill(123, 'flag1', 'off') + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION) + expect(event.metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE) + expect(event.metadata.names).to eq(['flag1']) + + repository.update([], [{name: 'flag1', trafficTypeName: 'tt_name_1', killed: false, default_treatment: 'on', changeNumber: 1, conditions: []}], -1) + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED) + expect(event.metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE) + expect(event.metadata.names).to eq(['flag1']) + end end describe 'with Memory Adapter' do From 919bb8e427e156026cadcfb997e7ded52175cef2 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Mon, 2 Mar 2026 13:00:30 -0800 Subject: [PATCH 32/46] added firing sdk ready event --- lib/splitclient-rb/engine/status_manager.rb | 7 ++++++- spec/engine/status_manager_spec.rb | 19 ++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/splitclient-rb/engine/status_manager.rb b/lib/splitclient-rb/engine/status_manager.rb index c0115ffe..bfe35f9b 100644 --- a/lib/splitclient-rb/engine/status_manager.rb +++ b/lib/splitclient-rb/engine/status_manager.rb @@ -3,9 +3,10 @@ module SplitIoClient module Engine class StatusManager - def initialize(config) + def initialize(config, internal_events_queue) @config = config @sdk_ready = Concurrent::CountDownLatch.new(1) + @internal_events_queue = internal_events_queue end def ready? @@ -19,6 +20,10 @@ def ready! @sdk_ready.count_down @config.logger.info('SplitIO SDK is ready') + @internal_events_queue.push( + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil) + ) end def wait_until_ready(seconds = nil) diff --git a/spec/engine/status_manager_spec.rb b/spec/engine/status_manager_spec.rb index 3805a21a..c7a6a62d 100644 --- a/spec/engine/status_manager_spec.rb +++ b/spec/engine/status_manager_spec.rb @@ -6,15 +6,16 @@ subject { SplitIoClient::Engine::StatusManager } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) } + let(:queue) {Queue.new} it 'check if sdk is ready - should return false' do - status_manager = subject.new(config) + status_manager = subject.new(config, queue) expect(status_manager.ready?).to eq(false) end it 'check if sdk is ready - should return true' do - status_manager = subject.new(config) + status_manager = subject.new(config, queue) expect(status_manager.ready?).to eq(false) @@ -23,11 +24,23 @@ end it 'wait until ready - should return false' do - status_manager = subject.new(config) + status_manager = subject.new(config, queue) expect { status_manager.wait_until_ready(0.5) }.to raise_error(SplitIoClient::SplitIoError, 'SDK start up timeout expired') status_manager.ready! expect { status_manager.wait_until_ready(0) }.not_to raise_error end + + it 'check if sdk is ready - should fire ready event' do + status_manager = subject.new(config, queue) + + expect(status_manager.ready?).to eq(false) + + status_manager.ready! + expect(status_manager.ready?).to eq(true) + event = queue.pop + expect(event.internal_event).to be(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY) + expect(event.metadata).to be(nil) + end end From 55e477adb4b997e5c3c3541cd879d624b6bd1512 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Tue, 3 Mar 2026 21:39:50 -0800 Subject: [PATCH 33/46] added events manager class --- lib/splitclient-rb.rb | 3 + .../engine/events/events_manager.rb | 187 ++++++++++++++++++ .../engine/events/events_task.rb | 6 +- .../models/event_active_subscriptions.rb | 14 ++ .../engine/models/valid_sdk_event.rb | 14 ++ lib/splitclient-rb/engine/status_manager.rb | 3 +- 6 files changed, 223 insertions(+), 4 deletions(-) create mode 100644 lib/splitclient-rb/engine/events/events_manager.rb create mode 100644 lib/splitclient-rb/engine/models/event_active_subscriptions.rb create mode 100644 lib/splitclient-rb/engine/models/valid_sdk_event.rb diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index 67688324..b9c0223c 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -67,6 +67,7 @@ require 'splitclient-rb/engine/common/impressions_manager' require 'splitclient-rb/engine/common/noop_impressions_counter' require 'splitclient-rb/engine/events/events_manager_config.rb' +require 'splitclient-rb/engine/events/events_manager.rb' require 'splitclient-rb/engine/events/events_task.rb' require 'splitclient-rb/engine/parser/condition' require 'splitclient-rb/engine/parser/partition' @@ -119,6 +120,8 @@ require 'splitclient-rb/engine/models/sdk_event.rb' require 'splitclient-rb/engine/models/sdk_internal_event.rb' require 'splitclient-rb/engine/models/sdk_internal_event_notification.rb' +require 'splitclient-rb/engine/models/valid_sdk_event.rb' +require 'splitclient-rb/engine/models/event_active_subscriptions.rb' require 'splitclient-rb/engine/auth_api_client' require 'splitclient-rb/engine/back_off' require 'splitclient-rb/engine/fallback_treatment_calculator.rb' diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb new file mode 100644 index 00000000..c710c487 --- /dev/null +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -0,0 +1,187 @@ +# frozen_string_literal: true + +module SplitIoClient + module Engine + module Events + class EventsManager + def initialize(events_configurations, events_delivery, config) + @events_configurations = events_configurations + @events_delivery = events_delivery + @active_subscriptions = {} + @internal_events_status = {} + @mutex = Mutex.new + @config = config + end + + def register(sdk_event, event_handler) + return unless !@active_subscriptions.key?(sdk_event) || get_event_handler(sdk_event).nil? + + @mutex.synchronize do + # SDK ready already fired + if sdk_event == SdkEvent.SDK_READY && event_already_triggered(sdk_event) + @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, event_handler) + @config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled + fire_sdk_event(sdk_event, nil) + return + end + + @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(false, event_handler) + end + end + + def unregister(sdk_event) + return unless @active_subscriptions.key?(sdk_event) + + @mutex.synchronize do + @active_subscriptions.delete(sdk_event) + end + end + + def notify_internal_event(sdk_internal_event, event_metadata) + @mutex.synchronize do + update_internal_event_status(sdk_internal_event, true) + @manager_config.evaluation_order.each do |sorted_event| + if get_sdk_event_if_applicable(sdk_internal_event).include?(sorted_event) && + !get_event_handler(sorted_event).nil? + fire_sdk_event(sorted_event, event_metadata) + end + + # if client is not subscribed to SDK_READY + if sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && get_event_handler(sorted_event).nil? + @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled + @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, None) + end + end + end + end + + def destroy + @mutex.synchronize do + @active_subscriptions = {} + @internal_events_status = {} + end + end + + private + + def fire_sdk_event(sdk_event, event_metadata) + @config.logger.debug("EventsManager: Firing Sdk event: #{sdk_event}") if @config.debug_enabled + @config.threads[:sdk_event_notify] = Thread.new do + @events_delivery.deliver(sdk_event, event_metadata, get_event_handler(sdk_event)) + end + sdk_event_triggered(sdk_event) + end + + def event_already_triggered(sdk_event) + return @active_subscriptions[sdk_event].triggered if @active_subscriptions.key?(sdk_event) + + false + end + + def get_internal_event_status(sdk_internal_event) + return @internal_events_status[sdk_internal_event] if @internal_events_status.key?(sdk_internal_event) + + false + end + + def update_internal_event_status(sdk_internal_event, status) + @internal_events_status[sdk_internal_event] = status + end + + def sdk_event_triggered(sdk_event) + return unless @active_subscriptions.key?(sdk_event) + + return if @active_subscriptions[sdk_event].triggered + + @active_subscriptions[sdk_event].triggered = true + end + + def get_event_handler(sdk_event) + return nil unless @active_subscriptions.key?(sdk_event) + + @active_subscriptions[sdk_event].handler + end + + def get_sdk_event_if_applicable(sdk_internal_event) + final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(None, false) + + events_to_fire = [] + require_any_sdk_event = check_require_any(sdk_internal_event) + if require_any_sdk_event.valid + if (!event_already_triggered(require_any_sdk_event.sdk_event) && + execution_limit(require_any_sdk_event.sdk_event) == 1) || + execution_limit(require_any_sdk_event.sdk_event) == -1 + final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new( + require_any_sdk_event.sdk_event, + check_prerequisites(require_any_sdk_event.sdk_event) && + check_suppressed_by(require_any_sdk_event.sdk_event) + ) + end + events_to_fire.push(final_sdk_event.sdk_event) if final_sdk_event.valid + end + check_require_all.each { |sdk_event| events_to_fire.push(sdk_event) } + + events_to_fire + end + + def check_require_all + events = [] + @manager_config.require_all.each do |require_name, require_value| + final_status = true + require_value.each { |val| final_status &= get_internal_event_status(val) } + events.push(require_name) if check_event_eligible_conditions(final_status, require_name, require_value) + end + + events + end + + def check_event_eligible_conditions(final_status, require_name, require_value) + final_status && + check_prerequisites(require_name) && + ((!event_already_triggered(require_name) && + execution_limit(require_name) == 1) || + execution_limit(require_name) == -1) && + len(require_value).positive? + end + + def check_prerequisites(sdk_event) + @manager_config.prerequisites.each do |name, value| + value.each do |val| + return false if name == sdk_event && !event_already_triggered(val) + end + end + + true + end + + def check_suppressed_by(sdk_event) + @manager_config.suppressed_by.each do |name, value| + value.each do |val| + return false if name == sdk_event && event_already_triggered(val) + end + end + + true + end + + def execution_limit(sdk_event) + return -1 if @manager_config.execution_limits.key?(sdk_event) + + @manager_config.execution_limits[sdk_event] + end + + def check_require_any(sdk_internal_event) + valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(None, false) + @manager_config.require_any.each do |name, val| + if val.include?(sdk_internal_event) + valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true) + return valid_sdk_event + end + end + + valid_sdk_event + end + end + end + end +end diff --git a/lib/splitclient-rb/engine/events/events_task.rb b/lib/splitclient-rb/engine/events/events_task.rb index 42bc736b..f1cdb245 100644 --- a/lib/splitclient-rb/engine/events/events_task.rb +++ b/lib/splitclient-rb/engine/events/events_task.rb @@ -16,7 +16,7 @@ def initialize(notify_internal_events, internal_events_queue, config) def start return if @running - @config.logger.info('Starting Internal Events Task.') if @config.debug_enabled + @config.logger.info('Starting Internal Events Task.') @running = true @config.threads[:internal_events_task] = Thread.new do worker_thread @@ -26,7 +26,7 @@ def start def stop return unless @running - @config.logger.info('Stopping Internal Events Task.') if @config.debug_enabled + @config.logger.info('Stopping Internal Events Task.') @running = false end @@ -36,7 +36,7 @@ def worker_thread while (event = @internal_events_queue.pop) break unless @running - @config.logger.info("Processing sdk internal event: #{event.internal_event}") if @config.debug_enabled + @config.logger.debug("Processing sdk internal event: #{event.internal_event}") if @config.debug_enabled begin @notify_internal_events.call(event.internal_event, event.metadata) rescue StandardError => e diff --git a/lib/splitclient-rb/engine/models/event_active_subscriptions.rb b/lib/splitclient-rb/engine/models/event_active_subscriptions.rb new file mode 100644 index 00000000..9a1b5a88 --- /dev/null +++ b/lib/splitclient-rb/engine/models/event_active_subscriptions.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: false + +module SplitIoClient + module Engine::Models + class EventsActiveSubscriptions + attr_reader :triggered, :handler + + def initialize(triggered, handler) + @triggered = triggered + @handler = handler + end + end + end +end diff --git a/lib/splitclient-rb/engine/models/valid_sdk_event.rb b/lib/splitclient-rb/engine/models/valid_sdk_event.rb new file mode 100644 index 00000000..0a0cbcd0 --- /dev/null +++ b/lib/splitclient-rb/engine/models/valid_sdk_event.rb @@ -0,0 +1,14 @@ +# frozen_string_literal: false + +module SplitIoClient + module Engine::Models + class ValidSdkEvent + attr_reader :sdk_event, :valid + + def initialize(sdk_event, valid) + @sdk_event = sdk_event + @valid = valid + end + end + end +end diff --git a/lib/splitclient-rb/engine/status_manager.rb b/lib/splitclient-rb/engine/status_manager.rb index bfe35f9b..e5ed19d1 100644 --- a/lib/splitclient-rb/engine/status_manager.rb +++ b/lib/splitclient-rb/engine/status_manager.rb @@ -22,7 +22,8 @@ def ready! @config.logger.info('SplitIO SDK is ready') @internal_events_queue.push( SplitIoClient::Engine::Models::SdkInternalEventNotification.new( - SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil) + SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil + ) ) end From 394b9fc47590f29c0c3fcfa708d30f37b36945d0 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 4 Mar 2026 11:06:21 -0800 Subject: [PATCH 34/46] added events delivery and tests --- lib/splitclient-rb.rb | 1 + .../engine/events/events_delivery.rb | 23 +++ .../engine/events/events_manager.rb | 16 +- .../models/event_active_subscriptions.rb | 4 +- spec/engine/events/events_delivery_spec.rb | 41 +++++ spec/engine/events/events_manager_spec.rb | 159 ++++++++++++++++++ 6 files changed, 234 insertions(+), 10 deletions(-) create mode 100644 lib/splitclient-rb/engine/events/events_delivery.rb create mode 100644 spec/engine/events/events_delivery_spec.rb create mode 100644 spec/engine/events/events_manager_spec.rb diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index b9c0223c..5a50b64f 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -69,6 +69,7 @@ require 'splitclient-rb/engine/events/events_manager_config.rb' require 'splitclient-rb/engine/events/events_manager.rb' require 'splitclient-rb/engine/events/events_task.rb' +require 'splitclient-rb/engine/events/events_delivery.rb' require 'splitclient-rb/engine/parser/condition' require 'splitclient-rb/engine/parser/partition' require 'splitclient-rb/engine/parser/evaluator' diff --git a/lib/splitclient-rb/engine/events/events_delivery.rb b/lib/splitclient-rb/engine/events/events_delivery.rb new file mode 100644 index 00000000..205ef187 --- /dev/null +++ b/lib/splitclient-rb/engine/events/events_delivery.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module SplitIoClient + module Engine + module Events + class EventsDelivery + + def initialize(config) + @config = config + end + + def deliver(sdk_event, event_metadata, event_handler) + begin + event_handler.call(event_metadata) + rescue StandardError => e + @config.logger.error("Exception when calling handler for Sdk Event #{sdk_event}") + @config.log_found_exception(__method__.to_s, e) + end + end + end + end + end +end diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb index c710c487..ae4e9abc 100644 --- a/lib/splitclient-rb/engine/events/events_manager.rb +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -4,8 +4,8 @@ module SplitIoClient module Engine module Events class EventsManager - def initialize(events_configurations, events_delivery, config) - @events_configurations = events_configurations + def initialize(events_manager_config, events_delivery, config) + @manager_config = events_manager_config @events_delivery = events_delivery @active_subscriptions = {} @internal_events_status = {} @@ -18,7 +18,7 @@ def register(sdk_event, event_handler) @mutex.synchronize do # SDK ready already fired - if sdk_event == SdkEvent.SDK_READY && event_already_triggered(sdk_event) + if sdk_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event) @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, event_handler) @config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled fire_sdk_event(sdk_event, nil) @@ -49,7 +49,7 @@ def notify_internal_event(sdk_internal_event, event_metadata) # if client is not subscribed to SDK_READY if sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && get_event_handler(sorted_event).nil? @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled - @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, None) + @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil) end end end @@ -103,7 +103,7 @@ def get_event_handler(sdk_event) end def get_sdk_event_if_applicable(sdk_internal_event) - final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(None, false) + final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false) events_to_fire = [] require_any_sdk_event = check_require_any(sdk_internal_event) @@ -141,7 +141,7 @@ def check_event_eligible_conditions(final_status, require_name, require_value) ((!event_already_triggered(require_name) && execution_limit(require_name) == 1) || execution_limit(require_name) == -1) && - len(require_value).positive? + require_value.length.positive? end def check_prerequisites(sdk_event) @@ -165,13 +165,13 @@ def check_suppressed_by(sdk_event) end def execution_limit(sdk_event) - return -1 if @manager_config.execution_limits.key?(sdk_event) + return -1 unless @manager_config.execution_limits.key?(sdk_event) @manager_config.execution_limits[sdk_event] end def check_require_any(sdk_internal_event) - valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(None, false) + valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false) @manager_config.require_any.each do |name, val| if val.include?(sdk_internal_event) valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true) diff --git a/lib/splitclient-rb/engine/models/event_active_subscriptions.rb b/lib/splitclient-rb/engine/models/event_active_subscriptions.rb index 9a1b5a88..3a0d4909 100644 --- a/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +++ b/lib/splitclient-rb/engine/models/event_active_subscriptions.rb @@ -2,8 +2,8 @@ module SplitIoClient module Engine::Models - class EventsActiveSubscriptions - attr_reader :triggered, :handler + class EventActiveSubscriptions + attr_accessor :triggered, :handler def initialize(triggered, handler) @triggered = triggered diff --git a/spec/engine/events/events_delivery_spec.rb b/spec/engine/events/events_delivery_spec.rb new file mode 100644 index 00000000..5583c0e6 --- /dev/null +++ b/spec/engine/events/events_delivery_spec.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::Engine::Events::EventsDelivery do + subject { SplitIoClient::Engine::Events::EventsDelivery } + let(:metadata) { nil } + + it 'test calling handler' do + config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) + delivery = subject.new(config) + + delivery.deliver(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE), + method(:call_back) + ) + sleep 0.5 + expect(@metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE) + end + + it 'test exception when calling handler' do + log = StringIO.new + config = SplitIoClient::SplitConfig.new(logger: Logger.new(log)) + delivery = subject.new(config) + + delivery.deliver(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE), + method(:call_with_exception) + ) + sleep 0.5 + expect(log.string).to include 'Exception when calling handler for Sdk Event' \ + end + + def call_back(metadata) + @metadata = metadata + end + + def call_with_exception(metadata) + raise StandardError("call exception") + end +end diff --git a/spec/engine/events/events_manager_spec.rb b/spec/engine/events/events_manager_spec.rb new file mode 100644 index 00000000..0ba9c43d --- /dev/null +++ b/spec/engine/events/events_manager_spec.rb @@ -0,0 +1,159 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe SplitIoClient::Engine::Events::EventsManager do + subject { SplitIoClient::Engine::Events::EventsManager } + let(:metadata) { nil } + let(:sdk_ready) { false } + let(:sdk_update) { false } + let(:first_event) { nil } + + + it 'test_firing_events' do + config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) + manager = subject.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + manager.register(SplitIoClient::Engine::Models::SdkEvent::SDK_READY, method(:ready_call_back)) + manager.register(SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE, method(:update_call_back)) + meta = SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, ["feature1"]) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil) + sleep 0.5 + expect(@metadata).to be(nil) + expect(@sdk_ready).to be(true) + expect(@sdk_update).to be(false) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, meta) + sleep 0.5 + expect(@metadata).to eq(meta) + expect(@sdk_update).to be(true) + expect(@sdk_ready).to be(false) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION, meta) + sleep 0.5 + expect(@metadata).to eq(meta) + expect(@sdk_update).to be(true) + expect(@sdk_ready).to be(false) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED, meta) + sleep 0.5 + expect(@metadata).to eq(meta) + expect(@sdk_update).to be(true) + expect(@sdk_ready).to be(false) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED, meta) + sleep 0.5 + expect(@metadata).to eq(meta) + expect(@sdk_update).to be(true) + expect(@sdk_ready).to be(false) + end + + it 'events fire only after register' do + config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) + manager = subject.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + meta = SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, ["feature1"]) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil) + sleep 0.5 + expect(@metadata).to be(nil) + expect(@sdk_ready).to be(false) + expect(@sdk_update).to be(false) + + manager.register(SplitIoClient::Engine::Models::SdkEvent::SDK_READY, method(:ready_call_back)) + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil) + sleep 0.5 + expect(@metadata).to be(nil) + expect(@sdk_ready).to be(true) + expect(@sdk_update).to be(false) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, meta) + sleep 0.5 + expect(@metadata).to eq(nil) + expect(@sdk_update).to be(false) + expect(@sdk_ready).to be(false) + + manager.register(SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE, method(:update_call_back)) + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, meta) + sleep 0.5 + expect(@metadata).to eq(meta) + expect(@sdk_update).to be(true) + expect(@sdk_ready).to be(false) + end + + it 'update fires only after ready events' do + config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) + manager = subject.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + manager.register(SplitIoClient::Engine::Models::SdkEvent::SDK_READY, method(:ready_call_back)) + manager.register(SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE, method(:update_call_back)) + meta = SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, ["feature1"]) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, meta) + sleep 0.5 + expect(@metadata).to eq(nil) + expect(@sdk_update).to be(false) + expect(@sdk_ready).to be(false) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil) + sleep 0.5 + expect(@metadata).to be(nil) + expect(@sdk_ready).to be(true) + expect(@sdk_update).to be(false) + + reset_flags + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, meta) + sleep 0.5 + expect(@metadata).to eq(meta) + expect(@sdk_update).to be(true) + expect(@sdk_ready).to be(false) + end + + it 'event ordered correctly' do + config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) + manager = subject.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + manager.register(SplitIoClient::Engine::Models::SdkEvent::SDK_READY, method(:ready_call_back)) + manager.register(SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE, method(:update_call_back)) + meta = SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, ["feature1"]) + + reset_flags + @first_event = nil + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil) + manager.notify_internal_event(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, meta) + sleep 0.5 + expect(@first_event).to be("ready") + end + + def ready_call_back(metadata) + @sdk_ready = true + @metadata = metadata + @first_event = "ready" if @first_event.nil? + end + + def update_call_back(metadata) + @sdk_update = true + @metadata = metadata + @first_event = "update" if @first_event.nil? + end + + def reset_flags + @sdk_ready = false + @sdk_update = false + @metadata = nil + end +end From 37fd6f3ec0bb2945439e9261ed4258991c66c076 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 4 Mar 2026 11:07:54 -0800 Subject: [PATCH 35/46] polish --- lib/splitclient-rb/engine/events/events_delivery.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/splitclient-rb/engine/events/events_delivery.rb b/lib/splitclient-rb/engine/events/events_delivery.rb index 205ef187..dd2a093b 100644 --- a/lib/splitclient-rb/engine/events/events_delivery.rb +++ b/lib/splitclient-rb/engine/events/events_delivery.rb @@ -4,18 +4,15 @@ module SplitIoClient module Engine module Events class EventsDelivery - def initialize(config) @config = config end def deliver(sdk_event, event_metadata, event_handler) - begin - event_handler.call(event_metadata) - rescue StandardError => e - @config.logger.error("Exception when calling handler for Sdk Event #{sdk_event}") - @config.log_found_exception(__method__.to_s, e) - end + event_handler.call(event_metadata) + rescue StandardError => e + @config.logger.error("Exception when calling handler for Sdk Event #{sdk_event}") + @config.log_found_exception(__method__.to_s, e) end end end From 79ab3d0c97f35008a1b200dcad6ff209673e29b7 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 5 Mar 2026 09:15:21 -0800 Subject: [PATCH 36/46] added test --- spec/engine/events/events_delivery_spec.rb | 101 ++++++++++++++------- 1 file changed, 67 insertions(+), 34 deletions(-) diff --git a/spec/engine/events/events_delivery_spec.rb b/spec/engine/events/events_delivery_spec.rb index 5583c0e6..e851d20d 100644 --- a/spec/engine/events/events_delivery_spec.rb +++ b/spec/engine/events/events_delivery_spec.rb @@ -4,38 +4,71 @@ describe SplitIoClient::Engine::Events::EventsDelivery do subject { SplitIoClient::Engine::Events::EventsDelivery } - let(:metadata) { nil } - - it 'test calling handler' do - config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) - delivery = subject.new(config) - - delivery.deliver(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, - SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE), - method(:call_back) - ) - sleep 0.5 - expect(@metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE) - end - - it 'test exception when calling handler' do - log = StringIO.new - config = SplitIoClient::SplitConfig.new(logger: Logger.new(log)) - delivery = subject.new(config) - - delivery.deliver(SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, - SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE), - method(:call_with_exception) - ) - sleep 0.5 - expect(log.string).to include 'Exception when calling handler for Sdk Event' \ - end - - def call_back(metadata) - @metadata = metadata - end - - def call_with_exception(metadata) - raise StandardError("call exception") - end + + it 'calls handler successfully' do + config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) + delivery = subject.new(config) + + delivery.deliver( + SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE), + method(:call_back) + ) + sleep 0.5 + expect(@metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE) + end + + it 'handles exception when calling handler' do + log = StringIO.new + config = SplitIoClient::SplitConfig.new(logger: Logger.new(log)) + delivery = subject.new(config) + + delivery.deliver( + SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE), + method(:call_with_exception) + ) + sleep 0.5 + expect(log.string).to include('Exception when calling handler for Sdk Event') + end + + it 'logs the sdk event name when handler raises exception' do + log = StringIO.new + config = SplitIoClient::SplitConfig.new(logger: Logger.new(log)) + delivery = subject.new(config) + + delivery.deliver( + SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, + nil, + method(:call_with_exception) + ) + sleep 0.5 + expect(log.string).to include('Exception when calling handler for Sdk Event') + expect(log.string).to include(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY.to_s) + end + + it 'calls handler with correct metadata' do + config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new)) + delivery = subject.new(config) + metadata = SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, + ['feature1', 'feature2'] + ) + + delivery.deliver( + SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + metadata, + method(:call_back) + ) + sleep 0.5 + expect(@metadata).to eq(metadata) + end + + def call_back(metadata) + @metadata = metadata + end + + def call_with_exception(_metadata) + raise StandardError, 'call exception' + end end From 58cb0b9c747cfdfb973bef82b46602cc1d920f36 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 5 Mar 2026 22:39:33 -0800 Subject: [PATCH 37/46] updated factory and split_client with integration tests --- lib/splitclient-rb.rb | 1 + lib/splitclient-rb/clients/split_client.rb | 11 +++- .../engine/api/faraday_middleware/gzip.rb | 1 + .../engine/events/events_manager.rb | 3 +- .../engine/events/noop_events_queue.rb | 13 ++++ lib/splitclient-rb/split_factory.rb | 25 ++++++-- lib/splitclient-rb/version.rb | 2 +- spec/integrations/push_client_spec.rb | 63 +++++++++++++++++-- 8 files changed, 107 insertions(+), 12 deletions(-) create mode 100644 lib/splitclient-rb/engine/events/noop_events_queue.rb diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index 5a50b64f..ee3d7b23 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -70,6 +70,7 @@ require 'splitclient-rb/engine/events/events_manager.rb' require 'splitclient-rb/engine/events/events_task.rb' require 'splitclient-rb/engine/events/events_delivery.rb' +require 'splitclient-rb/engine/events/noop_events_queue.rb' require 'splitclient-rb/engine/parser/condition' require 'splitclient-rb/engine/parser/partition' require 'splitclient-rb/engine/parser/evaluator' diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index 2caccf84..c4abe7eb 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -18,7 +18,7 @@ class SplitClient # @param sdk_key [String] the SDK key for your split account # # @return [SplitIoClient] split.io client instance - def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator, fallback_treatment_calculator) + def initialize(sdk_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer, evaluator, split_validator, fallback_treatment_calculator, events_manager) @api_key = sdk_key @splits_repository = repositories[:splits] @segments_repository = repositories[:segments] @@ -33,6 +33,7 @@ def initialize(sdk_key, repositories, status_manager, config, impressions_manage @split_validator = split_validator @evaluator = evaluator @fallback_treatment_calculator = fallback_treatment_calculator + @events_manager = events_manager end def get_treatment( @@ -176,6 +177,14 @@ def block_until_ready(time = nil) @status_manager.wait_until_ready(time) if @status_manager end + def register(sdk_event, handler) + @events_manager.register(sdk_event, handler) + end + + def unregister(sdk_event, handler) + @events_manager.unregister(sdk_event) + end + private def check_properties_size(properties_size, msg = "Event not queued") diff --git a/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb b/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb index e1e6baf2..fad8471b 100644 --- a/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +++ b/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'faraday' +require 'stringio' module SplitIoClient module FaradayMiddleware diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb index ae4e9abc..c2d687f0 100644 --- a/lib/splitclient-rb/engine/events/events_manager.rb +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -14,7 +14,7 @@ def initialize(events_manager_config, events_delivery, config) end def register(sdk_event, event_handler) - return unless !@active_subscriptions.key?(sdk_event) || get_event_handler(sdk_event).nil? + return if @active_subscriptions.key?(sdk_event) && !get_event_handler(sdk_event).nil? @mutex.synchronize do # SDK ready already fired @@ -25,6 +25,7 @@ def register(sdk_event, event_handler) return end + @config.logger.debug("EventsManager: Register event: #{sdk_event}") if @config.debug_enabled @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(false, event_handler) end end diff --git a/lib/splitclient-rb/engine/events/noop_events_queue.rb b/lib/splitclient-rb/engine/events/noop_events_queue.rb new file mode 100644 index 00000000..48989ac4 --- /dev/null +++ b/lib/splitclient-rb/engine/events/noop_events_queue.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module SplitIoClient + module Engine + module Events + class NoOpEventsQueue + def push(sdk_event) + # do nothing + end + end + end + end +end diff --git a/lib/splitclient-rb/split_factory.rb b/lib/splitclient-rb/split_factory.rb index ca8fb1bb..5500d043 100644 --- a/lib/splitclient-rb/split_factory.rb +++ b/lib/splitclient-rb/split_factory.rb @@ -45,6 +45,7 @@ def initialize(api_key, config_hash = {}) register_factory + build_events_manager build_telemetry_components build_flag_sets_filter build_repositories @@ -53,13 +54,13 @@ def initialize(api_key, config_hash = {}) build_unique_keys_tracker build_impressions_components - @status_manager = Engine::StatusManager.new(@config) + @status_manager = Engine::StatusManager.new(@config, @internal_events_queue) @split_validator = SplitIoClient::Validators.new(@config) @evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @rule_based_segment_repository, @config) start! fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(@config.fallback_treatments_configuration) - @client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer, @evaluator, @split_validator, fallback_treatment_calculator) + @client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer, @evaluator, @split_validator, fallback_treatment_calculator, @events_manager) @manager = SplitManager.new(@splits_repository, @status_manager, @config) end @@ -219,9 +220,9 @@ def build_repositories else @flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new(@config.flag_sets_filter) end - @splits_repository = SplitsRepository.new(@config, @flag_sets_repository, @flag_sets_filter) - @segments_repository = SegmentsRepository.new(@config) - @rule_based_segment_repository = RuleBasedSegmentsRepository.new(@config) + @splits_repository = SplitsRepository.new(@config, @flag_sets_repository, @flag_sets_filter, @internal_events_queue) + @segments_repository = SegmentsRepository.new(@config, @internal_events_queue) + @rule_based_segment_repository = RuleBasedSegmentsRepository.new(@config, @internal_events_queue) @impressions_repository = ImpressionsRepository.new(@config) @events_repository = EventsRepository.new(@config, @api_key, @runtime_producer) end @@ -265,5 +266,19 @@ def build_impressions_components def build_flag_sets_filter @flag_sets_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(@config.flag_sets_filter) end + + def build_events_manager + @events_manager = Engine::Events::EventsManager.new(Engine::Events::EventsManagerConfig.new, + Engine::Events::EventsDelivery.new(@config), + @config) + if @config.consumer? + @internal_events_queue = Engine::Events::NoOpEventsQueue.new + return + end + + @internal_events_queue = Queue.new + @events_task = Engine::Events::EventsTask.new(@events_manager.method(:notify_internal_event), @internal_events_queue, @config) + @events_task.start + end end end diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index 82ab6583..5b66f95f 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.10.1' + VERSION = '8.11.0' end diff --git a/spec/integrations/push_client_spec.rb b/spec/integrations/push_client_spec.rb index 689793a5..9420861f 100644 --- a/spec/integrations/push_client_spec.rb +++ b/spec/integrations/push_client_spec.rb @@ -39,6 +39,9 @@ let(:auth_body_response) do File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/auth_body_response.json')) end + let(:metadata) { nil } + let(:event_ready) { false } + let(:sdk_update) { false } before do stub_request(:any, /https:\/\/events.*/).to_return(status: 200, body: '') @@ -155,21 +158,25 @@ stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) mock_server do |server| - + log = StringIO.new streaming_service_url = server.base_uri factory = SplitIoClient::SplitFactory.new( 'test_api_key', streaming_service_url: streaming_service_url, - auth_service_url: auth_service_url + auth_service_url: auth_service_url, + logger: Logger.new(log), + debug_enabled: true ) + reset_flags client = factory.client + client.register(SplitIoClient::Engine::Models::SdkEvent::SDK_READY, method(:ready_call_back)) + client.register(SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE, method(:update_call_back)) client.block_until_ready server.setup_response('/') do |_, res| send_content(res, event_split_iff_update_no_compression) end - treatment = 'control' for i in 1..10 do sleep(1) @@ -177,7 +184,25 @@ break if treatment != 'control' end + expect(@metadata.length).to eq(2) + check1 = false + check2 = false + @metadata.each do |meta| + if meta.type == SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE + expect(meta.names).to eq(['bilal_split']) + check1 = true + end + if meta.type == SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE + expect(meta.names).to eq([]) + check2 = true + end + end + expect(check1).to be(true) + expect(check2).to be(true) + expect(@event_ready).to be(true) + expect(@sdk_update).to eq(true) expect(treatment).to eq('off') + client.destroy end end @@ -319,20 +344,35 @@ stub_request(:get, auth_service_url + "?s=1.3").to_return(status: 200, body: auth_body_response) streaming_service_url = server.base_uri + log = StringIO.new factory = SplitIoClient::SplitFactory.new( 'test_api_key', streaming_enabled: true, streaming_service_url: streaming_service_url, - auth_service_url: auth_service_url + auth_service_url: auth_service_url, + logger: Logger.new(log), + debug_enabled: true ) + reset_flags client = factory.client + client.register(SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE, method(:update_call_back)) client.block_until_ready(1) sleep(2) expect(client.get_treatment('admin', 'push_test')).to eq('on') expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=-1&rbSince=-1')).to have_been_made.times(1) expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850109&rbSince=-1')).to have_been_made.times(1) expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?s=1.3&since=1585948850110&rbSince=-1')).to have_been_made.times(0) + + check1 = false + @metadata.each do |meta| + if meta.type == SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE + expect(meta.names.include?('push_test')).to eq(true) + check1 = true + end + end + expect(check1).to eq(true) + client.destroy end end @@ -572,4 +612,19 @@ 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 + + def ready_call_back(metadata) + @event_ready = true + end + + def update_call_back(metadata) + @sdk_update = true + @metadata.push(metadata) + end + + def reset_flags + @event_ready = false + @sdk_update = false + @metadata = [] + end end From bf98f2659b283b6db5aff117b157ddf275fa88f9 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Fri, 6 Mar 2026 09:05:40 -0800 Subject: [PATCH 38/46] updated tests --- .../clients/split_client_spec.rb | 13 +-- spec/cache/fetchers/segment_fetch_spec.rb | 14 ++-- spec/cache/fetchers/split_fetch_spec.rb | 15 ++-- .../stores/localhost_split_store_spec.rb | 4 +- spec/engine/api/segments_spec.rb | 3 +- .../rule_based_segment_matcher_spec.rb | 17 ++-- spec/engine/parser/evaluator_spec.rb | 7 +- spec/engine/push_manager_spec.rb | 7 +- spec/engine/sync_manager_spec.rb | 10 +-- spec/engine/synchronizer_spec.rb | 8 +- spec/repository_helper.rb | 17 ++-- spec/splitclient/split_client_spec.rb | 84 ++++++++++++------- spec/sse/event_source/client_spec.rb | 7 +- spec/sse/sse_handler_spec.rb | 7 +- spec/sse/workers/segments_worker_spec.rb | 7 +- spec/sse/workers/splits_worker_spec.rb | 25 +++--- spec/telemetry/synchronizer_spec.rb | 5 +- 17 files changed, 152 insertions(+), 98 deletions(-) diff --git a/spec/allocations/splitclient-rb/clients/split_client_spec.rb b/spec/allocations/splitclient-rb/clients/split_client_spec.rb index 40ab2543..5ceeeacf 100644 --- a/spec/allocations/splitclient-rb/clients/split_client_spec.rb +++ b/spec/allocations/splitclient-rb/clients/split_client_spec.rb @@ -4,13 +4,16 @@ describe SplitIoClient::SplitClient do let(:config) { SplitIoClient::SplitConfig.new(impressions_queue_size: 10) } - + let(:events_queue) { Queue.new } + let(:events_manager) { SplitIoClient::Engine::Events::EventsManager.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:impression_counter) { SplitIoClient::Engine::Common::ImpressionCounter.new } let(:evaluation_producer) { SplitIoClient::Telemetry::EvaluationProducer.new(config) } let(:impression_observer) { SplitIoClient::Observers::ImpressionObserver.new } @@ -42,7 +45,7 @@ unique_keys_tracker) end let(:client) do - SplitIoClient::SplitClient.new('', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => nil}, nil, config, impressions_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config), fallback_treatment_calculator) + SplitIoClient::SplitClient.new('', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => nil}, nil, config, impressions_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config), fallback_treatment_calculator, events_manager) end context 'control' do diff --git a/spec/cache/fetchers/segment_fetch_spec.rb b/spec/cache/fetchers/segment_fetch_spec.rb index 6280866b..8ee1bcdd 100644 --- a/spec/cache/fetchers/segment_fetch_spec.rb +++ b/spec/cache/fetchers/segment_fetch_spec.rb @@ -31,12 +31,13 @@ end context 'memory adapter' do + let(:events_queue) { Queue.new } let(:config) { SplitIoClient::SplitConfig.new } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } 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(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:segment_fetcher) { described_class.new(segments_repository, '', config, telemetry_runtime_producer) } let(:split_fetcher) do @@ -67,13 +68,14 @@ before do Redis.new.flushall end + let(:events_queue) { Queue.new } let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :redis) } let(:adapter) { SplitIoClient::Cache::Adapters::RedisAdapter.new(config.redis_url) } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(config) } 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(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:segment_fetcher) { described_class.new(segments_repository, '', config, telemetry_runtime_producer) } let(:split_fetcher) do diff --git a/spec/cache/fetchers/split_fetch_spec.rb b/spec/cache/fetchers/split_fetch_spec.rb index abe65d01..39e086c5 100644 --- a/spec/cache/fetchers/split_fetch_spec.rb +++ b/spec/cache/fetchers/split_fetch_spec.rb @@ -23,10 +23,11 @@ cache_adapter: :memory ) end + let(:events_queue) { Queue.new } 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(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:store) { described_class.new(splits_repository, rule_based_segments_repository, '', config, telemetry_runtime_producer) } @@ -77,10 +78,11 @@ flag_sets_filter: ['set_2'] ) end + let(:events_queue) { Queue.new } let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new(['set_2']) } let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new(['set_2']) } - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:store) { described_class.new(splits_repository, rule_based_segments_repository, '', config, telemetry_runtime_producer) } @@ -131,10 +133,11 @@ cache_adapter: :redis ) end + let(:events_queue) { Queue.new } let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(config) } 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(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:store) { described_class.new(splits_repository, rule_based_segments_repository, '', config, telemetry_runtime_producer) } diff --git a/spec/cache/stores/localhost_split_store_spec.rb b/spec/cache/stores/localhost_split_store_spec.rb index 73dbb90c..2458c4cf 100644 --- a/spec/cache/stores/localhost_split_store_spec.rb +++ b/spec/cache/stores/localhost_split_store_spec.rb @@ -3,11 +3,13 @@ require 'spec_helper' describe SplitIoClient::Cache::Stores::LocalhostSplitStore do + let(:log) { StringIO.new } + let(:events_queue) { Queue.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } 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(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } let(:split_file) do ['local_feature local_treatment'] diff --git a/spec/engine/api/segments_spec.rb b/spec/engine/api/segments_spec.rb index 755a2890..88891cd7 100644 --- a/spec/engine/api/segments_spec.rb +++ b/spec/engine/api/segments_spec.rb @@ -11,12 +11,13 @@ ) end let(:log) { StringIO.new } + let(:events_queue) { Queue.new } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:segments_api) { described_class.new('', segments_repository, config, telemetry_runtime_producer) } let(:adapter) do SplitIoClient::Cache::Adapters::MemoryAdapter.new(SplitIoClient::Cache::Adapters::MemoryAdapters::MapAdapter.new) end - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } let(:segments) do File.read(File.expand_path(File.join(File.dirname(__FILE__), '../../test_data/segments/segments.json'))) end diff --git a/spec/engine/matchers/rule_based_segment_matcher_spec.rb b/spec/engine/matchers/rule_based_segment_matcher_spec.rb index 6034dfc3..4ff2a255 100644 --- a/spec/engine/matchers/rule_based_segment_matcher_spec.rb +++ b/spec/engine/matchers/rule_based_segment_matcher_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' describe SplitIoClient::RuleBasedSegmentMatcher do + let(:events_queue) { Queue.new } let(:config) { SplitIoClient::SplitConfig.new(debug_enabled: true) } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } 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(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } context '#string_type' do it 'is not string type matcher' do @@ -17,14 +18,14 @@ context 'test_matcher' do it 'return false if excluded key is passed' do - rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) rbs_repositoy.update([{name: 'foo', trafficTypeName: 'tt_name_1', conditions: [], excluded: {keys: ['key1'], segments: []}}], [], -1) matcher = described_class.new(segments_repository, rbs_repositoy, 'foo', config) expect(matcher.match?(value: 'key1')).to be false end it 'return false if excluded segment is passed' do - rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rbs_repositoy, true) segments_repository.add_to_segment({:name => 'segment1', :added => [], :removed => []}) rbs_repositoy.update([{:name => 'foo', :trafficTypeName => 'tt_name_1', :conditions => [], :excluded => {:keys => ['key1'], :segments => [{:name => 'segment1', :type => 'standard'}]}}], [], -1) @@ -33,7 +34,7 @@ end it 'return false if excluded rb segment is matched' do - rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) rbs = {:name => 'sample_rule_based_segment', :trafficTypeName => 'tt_name_1', :conditions => [{ :matcherGroup => { :combiner => "AND", @@ -106,7 +107,7 @@ }] } - rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) rbs_repositoy.update([rule_based_segment], [], -1) matcher = described_class.new(segments_repository, rbs_repositoy, 'corge', config) expect(matcher.match?({:matching_key => 'user', :attributes => {}})).to be false @@ -114,7 +115,7 @@ end it 'return true if dependent rb segment matches' do - rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) rbs = { :changeNumber => 5, :name => "dependent_rbs", @@ -180,7 +181,7 @@ end it 'return true if has multiple conditions' do - rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rbs_repositoy = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) rbs = { :name => 'sample_rule_based_segment', :trafficTypeName => 'tt_name_1', diff --git a/spec/engine/parser/evaluator_spec.rb b/spec/engine/parser/evaluator_spec.rb index a6beabe9..fbf57acf 100644 --- a/spec/engine/parser/evaluator_spec.rb +++ b/spec/engine/parser/evaluator_spec.rb @@ -3,11 +3,12 @@ require 'spec_helper' describe SplitIoClient::Engine::Parser::Evaluator do - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(@default_config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(@default_config) } + let(:events_queue) { Queue.new } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(@default_config, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(@default_config, events_queue) } 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(@default_config, flag_sets_repository, flag_set_filter) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(@default_config, flag_sets_repository, flag_set_filter, events_queue) } let(:evaluator) { described_class.new(segments_repository, splits_repository, rule_based_segments_repository, true) } let(:killed_split) { { killed: true, defaultTreatment: 'default' } } diff --git a/spec/engine/push_manager_spec.rb b/spec/engine/push_manager_spec.rb index 4cc3f9db..bd3a3540 100644 --- a/spec/engine/push_manager_spec.rb +++ b/spec/engine/push_manager_spec.rb @@ -6,15 +6,16 @@ describe SplitIoClient::Engine::PushManager do subject { SplitIoClient::Engine::PushManager } + let(:events_queue) { Queue.new } let(:body_response) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/auth_body_response.json')) } let(:api_key) { 'PushManager-key' } let(:log) { StringIO.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, runtime_producer) } diff --git a/spec/engine/sync_manager_spec.rb b/spec/engine/sync_manager_spec.rb index 68169a7c..9bbcc13c 100644 --- a/spec/engine/sync_manager_spec.rb +++ b/spec/engine/sync_manager_spec.rb @@ -7,7 +7,7 @@ subject { SplitIoClient::Engine::SyncManager } let(:event_control) { "d4\r\nid: 123\nevent: message\ndata: {\"id\":\"1\",\"clientId\":\"emptyClientId\",\"timestamp\": 1582056812285,\"encoding\": \"json\",\"channel\": \"control_pri\",\"data\":\"{\\\"type\\\" : \\\"CONTROL\\\",\\\"controlType\\\":\\\"STREAMING_DISABLED\\\"}\"}\n\n\r\n" } - + let(:events_queue) { Queue.new } let(:splits) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) } let(:segment1) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment1.json')) } let(:segment2) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment2.json')) } @@ -18,9 +18,9 @@ let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log), streaming_enabled: true) } 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:events_repository) { SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer) } @@ -51,7 +51,7 @@ let(:telemetry_consumers) { { init: init_consumer, runtime: runtime_consumer, evaluation: evaluation_consumer } } let(:telemetry_api) { SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) } let(:telemetry_synchronizer) { SplitIoClient::Telemetry::Synchronizer.new(config, telemetry_consumers, init_producer, repositories, telemetry_api, 0, 0) } - let(:status_manager) { SplitIoClient::Engine::StatusManager.new(config) } + let(:status_manager) { SplitIoClient::Engine::StatusManager.new(config, events_queue) } let(:splits_worker) { SplitIoClient::SSE::Workers::SplitsWorker.new(synchronizer, config, splits_repository, telemetry_runtime_producer, sync_params[:segment_fetcher], rule_based_segments_repository) } let(:segments_worker) { SplitIoClient::SSE::Workers::SegmentsWorker.new(synchronizer, config, segments_repository) } let(:notification_processor) { SplitIoClient::SSE::NotificationProcessor.new(config, splits_worker, segments_worker) } diff --git a/spec/engine/synchronizer_spec.rb b/spec/engine/synchronizer_spec.rb index cef2020d..c00a3e90 100644 --- a/spec/engine/synchronizer_spec.rb +++ b/spec/engine/synchronizer_spec.rb @@ -4,7 +4,7 @@ describe SplitIoClient::Engine::Synchronizer do subject { SplitIoClient::Engine::Synchronizer } - + let(:events_queue) { Queue.new } let(:splits) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) } let(:segment1) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment1.json')) } let(:segment2) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment2.json')) } @@ -16,9 +16,9 @@ runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) - splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) - segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) - rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) + segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) repositories = { splits: splits_repository, diff --git a/spec/repository_helper.rb b/spec/repository_helper.rb index 37770a76..dc3157f8 100644 --- a/spec/repository_helper.rb +++ b/spec/repository_helper.rb @@ -3,6 +3,8 @@ require 'set' describe SplitIoClient::Helpers::RepositoryHelper do + let(:events_queue) { Queue.new } + context 'test repository helper' do it 'with flag set filter' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory) @@ -11,7 +13,8 @@ feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( config, flag_sets_repository, - flag_set_filter) + flag_set_filter, + events_queue) SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(true) @@ -33,7 +36,8 @@ feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( config, flag_sets_repository, - flag_set_filter) + flag_set_filter, + events_queue) SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(false) @@ -55,7 +59,8 @@ feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( config, flag_sets_repository, - flag_set_filter) + flag_set_filter, + events_queue) SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split1')[:impressionsDisabled]).to eq(false) @@ -74,7 +79,8 @@ feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( config, flag_sets_repository, - flag_set_filter) + flag_set_filter, + events_queue) SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split1').nil?).to eq(false) @@ -91,7 +97,8 @@ feature_flag_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new( config, flag_sets_repository, - flag_set_filter) + flag_set_filter, + events_queue) SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(feature_flag_repository, [{:name => 'split1', :status => 'ACTIVE', conditions: [], :sets => []}], -1, config, false) expect(feature_flag_repository.get_split('split1')[:prerequisites]).to eq([]) diff --git a/spec/splitclient/split_client_spec.rb b/spec/splitclient/split_client_spec.rb index c0596f23..be9805a9 100644 --- a/spec/splitclient/split_client_spec.rb +++ b/spec/splitclient/split_client_spec.rb @@ -4,20 +4,24 @@ describe SplitIoClient::SplitClient do context 'split client methods' do + let(:events_queue) { Queue.new } let(:config) { SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:events_manager) { SplitIoClient::Engine::Events::EventsManager.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } 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(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } let(:impressions_repository) {SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } 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, rule_based_segments_repository, config) } let(:fallback_treatment_calculator) { SplitIoClient::Engine::FallbackTreatmentCalculator.new(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new) } - let(:split_client) { SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository, :rule_based_segments => rule_based_segments_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config), fallback_treatment_calculator) } + let(:split_client) { SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository, :rule_based_segments => rule_based_segments_repository}, nil, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config), fallback_treatment_calculator, events_manager) } let(:splits) do File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) @@ -115,12 +119,16 @@ context 'impressions toggle' do it 'optimized mode' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :optimized) - segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) + events_queue = Queue.new + events_manager = SplitIoClient::Engine::Events::EventsManager.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) - splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) + splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) - rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -130,7 +138,7 @@ evaluation_producer = SplitIoClient::Telemetry::EvaluationProducer.new(config) evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new) - 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), fallback_treatment_calculator) + 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), fallback_treatment_calculator, events_manager) splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][0]], [], -1) @@ -156,12 +164,16 @@ it 'debug mode' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) - segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) + events_queue = Queue.new + events_manager = SplitIoClient::Engine::Events::EventsManager.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) - splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) + splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) - rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -171,7 +183,7 @@ evaluation_producer = SplitIoClient::Telemetry::EvaluationProducer.new(config) evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new) - 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), fallback_treatment_calculator) + 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), fallback_treatment_calculator, events_manager) splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][0]], [], -1) @@ -197,12 +209,16 @@ it 'none mode' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :none) - segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) + events_queue = Queue.new + events_manager = SplitIoClient::Engine::Events::EventsManager.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) - splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) + splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) - rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -212,7 +228,7 @@ evaluation_producer = SplitIoClient::Telemetry::EvaluationProducer.new(config) evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new) - 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), fallback_treatment_calculator) + 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), fallback_treatment_calculator, events_manager) splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) splits_repository.update([JSON.parse(splits,:symbolize_names => true)[:ff][:d][0]], [], -1) @@ -242,12 +258,16 @@ context 'fallback treatments' do it 'feature not found ' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) - segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) + events_queue = Queue.new + events_manager = SplitIoClient::Engine::Events::EventsManager.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) - splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) + splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) - rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -257,7 +277,7 @@ evaluation_producer = SplitIoClient::Telemetry::EvaluationProducer.new(config) evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(SplitIoClient::Engine::Models::FallbackTreatment.new("on-global", '{"prop": "global"}'), {:feature => SplitIoClient::Engine::Models::FallbackTreatment.new("on-local", '{"prop": "local"}')})) - 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), fallback_treatment_calculator) + 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), fallback_treatment_calculator, events_manager) treatment = split_client.get_treatment_with_config('key2', 'feature') expect(treatment[:treatment]).to eq('on-local') @@ -286,12 +306,16 @@ it 'exception' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) - segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) + events_queue = Queue.new + events_manager = SplitIoClient::Engine::Events::EventsManager.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) - splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) + splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) - rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -301,7 +325,7 @@ evaluation_producer = SplitIoClient::Telemetry::EvaluationProducer.new(config) evaluator = SplitIoClient::Engine::Parser::Evaluator.new(segments_repository, splits_repository, rule_based_segments_repository, config) fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(SplitIoClient::Engine::Models::FallbackTreatmentsConfiguration.new(SplitIoClient::Engine::Models::FallbackTreatment.new("on-global", '{"prop": "global"}'), {:feature => SplitIoClient::Engine::Models::FallbackTreatment.new("on-local", '{"prop": "local"}')})) - 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), fallback_treatment_calculator) + 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), fallback_treatment_calculator, events_manager) splits = File.read(File.join(SplitIoClient.root, 'spec/test_data/splits/imp-toggle.json')) split = JSON.parse(splits,:symbolize_names => true)[:ff][:d][0] @@ -343,12 +367,16 @@ it 'client not ready' do config = SplitIoClient::SplitConfig.new(cache_adapter: :memory, impressions_mode: :debug) - segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) + events_queue = Queue.new + events_manager = SplitIoClient::Engine::Events::EventsManager.new(SplitIoClient::Engine::Events::EventsManagerConfig.new, + SplitIoClient::Engine::Events::EventsDelivery.new(config), + config) + segments_repository = SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new([]) flag_set_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new([]) - splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) + splits_repository = SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) impressions_repository = SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) - rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rule_based_segments_repository = SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) runtime_producer = SplitIoClient::Telemetry::RuntimeProducer.new(config) events_repository = SplitIoClient::Cache::Repositories::EventsRepository.new(config, 'sdk_key', runtime_producer) impressions_counter = SplitIoClient::Engine::Common::ImpressionCounter.new @@ -367,7 +395,7 @@ def wait_until_ready(time) true end end - split_client = SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, MyStatusManager.new, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config), fallback_treatment_calculator) + split_client = SplitIoClient::SplitClient.new('sdk_key', {:splits => splits_repository, :segments => segments_repository, :impressions => impressions_repository, :events => events_repository}, MyStatusManager.new, config, impression_manager, evaluation_producer, evaluator, SplitIoClient::Validators.new(config), fallback_treatment_calculator, events_manager) treatment = split_client.get_treatment_with_config('key2', 'feature') expect(treatment[:treatment]).to eq('on-local') diff --git a/spec/sse/event_source/client_spec.rb b/spec/sse/event_source/client_spec.rb index 95e0a1f2..909cb6f5 100644 --- a/spec/sse/event_source/client_spec.rb +++ b/spec/sse/event_source/client_spec.rb @@ -8,6 +8,7 @@ subject { SplitIoClient::SSE::EventSource::Client } let(:log) { StringIO.new } + let(:events_queue) { Queue.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:telemetry_runtime_consumer) { SplitIoClient::Telemetry::RuntimeConsumer.new(config) } @@ -18,11 +19,11 @@ let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new([])} let(:repositories) do { - splits: SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter), - segments: SplitIoClient::Cache::Repositories::SegmentsRepository.new(config), + splits: SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue), + segments: SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue), impressions: SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config), events: SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer), - rule_based_segments: SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rule_based_segments: SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } end let(:parameters) do diff --git a/spec/sse/sse_handler_spec.rb b/spec/sse/sse_handler_spec.rb index 96fb6141..1c81abcf 100644 --- a/spec/sse/sse_handler_spec.rb +++ b/spec/sse/sse_handler_spec.rb @@ -8,11 +8,12 @@ let(:api_key) { 'SSEHandler-key' } let(:log) { StringIO.new } + let(:events_queue) { Queue.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:push_status_queue) { Queue.new } let(:notification_manager_keeper) { SplitIoClient::SSE::NotificationManagerKeeper.new(config, telemetry_runtime_producer, push_status_queue) } @@ -22,7 +23,7 @@ segments: segments_repository, impressions: SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config), events: SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer), - rule_based_segments: SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) + rule_based_segments: SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } end let(:parameters) do diff --git a/spec/sse/workers/segments_worker_spec.rb b/spec/sse/workers/segments_worker_spec.rb index 8e3405bc..918e4329 100644 --- a/spec/sse/workers/segments_worker_spec.rb +++ b/spec/sse/workers/segments_worker_spec.rb @@ -6,6 +6,7 @@ describe SplitIoClient::SSE::Workers::SegmentsWorker do subject { SplitIoClient::SSE::Workers::SegmentsWorker } + let(:events_queue) { Queue.new } let(:splits) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/splits.json')) } let(:segment1) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment1.json')) } let(:segment2) { File.read(File.join(SplitIoClient.root, 'spec/test_data/integrations/segment2.json')) } @@ -15,9 +16,9 @@ let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } 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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:impressions_repository) { SplitIoClient::Cache::Repositories::ImpressionsRepository.new(config) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:events_repository) { SplitIoClient::Cache::Repositories::EventsRepository.new(config, api_key, telemetry_runtime_producer) } diff --git a/spec/sse/workers/splits_worker_spec.rb b/spec/sse/workers/splits_worker_spec.rb index 74e00c51..b3318365 100644 --- a/spec/sse/workers/splits_worker_spec.rb +++ b/spec/sse/workers/splits_worker_spec.rb @@ -23,16 +23,17 @@ let(:event_split_update_segments) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c":2,"d":"eJzcVFtr20wQ/SvhPK9AsnzTvpnPJp+po0DlppRgzFga2dusJLNapaRG/73Id7sOoU+FvmluZ3TOGXYDayhNVTx9W3NIGUOiKtlAQCWQSNq+FyeJ6yzcBTuex+T0qe86XrfrUkJBzH4AgXw3mVFlivl3eiWIA/BA6yImq4oc0nPdG/mIOYF0gpYfeO3AEyh3Ca/XDfxer+u2BUpLtiohMfhvOn4aQeBFad20paRLFkg4pUrbqWGyGecWEvbwPQ9cCMQrypccVtmCDaTX7feCnu+7nY7nCZBeFpAtgbjIU7WszPbPSshNvc0lah8/b05hoxkkvv4/no4m42gKgYxsvGJzb4pqDdn0ZguVNwsxCIenhh3SPriBk/OSLB/Z/Vgpy1qV9mE3MSRLDfwxD/kMSjKVb1dUpmgwVFxgVtezWmBNxp5RsDdlavkdCJTqJ2+tqmcCmhasIU+LOEEtftfg8+Nk8vjlzxV44beINce2ME3z2TEeDrEWVzKNw3k0un8YhTd0aiaGnKqck4iXDakrwcpdNjzdq9PChxIV+VEXt2F/UUvTC9Guyk/t90dfO+/Xro73w65z7y6cU/ndnvTdge7f9W8wmcw/jb5F1+79yybsX6c7U2lGPat/BQAA//9ygdKB"}'), 'test') } let(:event_split_update_rb_segments) { SplitIoClient::SSE::EventSource::StreamData.new("data", 123, JSON.parse('{"type":"SPLIT_UPDATE","changeNumber":5564531221,"pcn":1234,"c":0,"d":"eyJjaGFuZ2VOdW1iZXIiOiAxMCwgInRyYWZmaWNUeXBlTmFtZSI6ICJ1c2VyIiwgIm5hbWUiOiAicmJzX2ZsYWciLCAidHJhZmZpY0FsbG9jYXRpb24iOiAxMDAsICJ0cmFmZmljQWxsb2NhdGlvblNlZWQiOiAxODI4Mzc3MzgwLCAic2VlZCI6IC0yODY2MTc5MjEsICJzdGF0dXMiOiAiQUNUSVZFIiwgImtpbGxlZCI6IGZhbHNlLCAiZGVmYXVsdFRyZWF0bWVudCI6ICJvZmYiLCAiYWxnbyI6IDIsICJjb25kaXRpb25zIjogW3siY29uZGl0aW9uVHlwZSI6ICJST0xMT1VUIiwgIm1hdGNoZXJHcm91cCI6IHsiY29tYmluZXIiOiAiQU5EIiwgIm1hdGNoZXJzIjogW3sia2V5U2VsZWN0b3IiOiB7InRyYWZmaWNUeXBlIjogInVzZXIifSwgIm1hdGNoZXJUeXBlIjogIklOX1JVTEVfQkFTRURfU0VHTUVOVCIsICJuZWdhdGUiOiBmYWxzZSwgInVzZXJEZWZpbmVkU2VnbWVudE1hdGNoZXJEYXRhIjogeyJzZWdtZW50TmFtZSI6ICJzYW1wbGVfcnVsZV9iYXNlZF9zZWdtZW50In19XX0sICJwYXJ0aXRpb25zIjogW3sidHJlYXRtZW50IjogIm9uIiwgInNpemUiOiAxMDB9LCB7InRyZWF0bWVudCI6ICJvZmYiLCAic2l6ZSI6IDB9XSwgImxhYmVsIjogImluIHJ1bGUgYmFzZWQgc2VnbWVudCBzYW1wbGVfcnVsZV9iYXNlZF9zZWdtZW50In0sIHsiY29uZGl0aW9uVHlwZSI6ICJST0xMT1VUIiwgIm1hdGNoZXJHcm91cCI6IHsiY29tYmluZXIiOiAiQU5EIiwgIm1hdGNoZXJzIjogW3sia2V5U2VsZWN0b3IiOiB7InRyYWZmaWNUeXBlIjogInVzZXIifSwgIm1hdGNoZXJUeXBlIjogIkFMTF9LRVlTIiwgIm5lZ2F0ZSI6IGZhbHNlfV19LCAicGFydGl0aW9ucyI6IFt7InRyZWF0bWVudCI6ICJvbiIsICJzaXplIjogMH0sIHsidHJlYXRtZW50IjogIm9mZiIsICJzaXplIjogMTAwfV0sICJsYWJlbCI6ICJkZWZhdWx0IHJ1bGUifV0sICJjb25maWd1cmF0aW9ucyI6IHt9LCAic2V0cyI6IFtdLCAiaW1wcmVzc2lvbnNEaXNhYmxlZCI6IGZhbHNlfQ=="}'), 'test') } let(:event_rb_segment_update) { SplitIoClient::SSE::EventSource::StreamData.new("data", 12345, JSON.parse('{"type":"RB_SEGMENT_UPDATE","changeNumber":5564531221,"pcn":1234,"c":0,"d":"eyJjaGFuZ2VOdW1iZXIiOiA1LCAibmFtZSI6ICJzYW1wbGVfcnVsZV9iYXNlZF9zZWdtZW50IiwgInN0YXR1cyI6ICJBQ1RJVkUiLCAidHJhZmZpY1R5cGVOYW1lIjogInVzZXIiLCAiZXhjbHVkZWQiOiB7ImtleXMiOiBbIm1hdXJvQHNwbGl0LmlvIl0sICJzZWdtZW50cyI6IFt7InR5cGUiOiAic3RhbmRhcmQiLCAibmFtZSI6ICJzZWdtZW50MSJ9XX0sICJjb25kaXRpb25zIjogW3sibWF0Y2hlckdyb3VwIjogeyJjb21iaW5lciI6ICJBTkQiLCAibWF0Y2hlcnMiOiBbeyJrZXlTZWxlY3RvciI6IHsidHJhZmZpY1R5cGUiOiAidXNlciIsICJhdHRyaWJ1dGUiOiAiZW1haWwifSwgIm1hdGNoZXJUeXBlIjogIklOX1NFR01FTlQiLCAibmVnYXRlIjogZmFsc2UsICJ1c2VyRGVmaW5lZFNlZ21lbnRNYXRjaGVyRGF0YSI6IHsic2VnbWVudE5hbWUiOiAiZGVtbyJ9fV19fV19"}'), 'test') } + let(:events_queue) { Queue.new } context 'add change number to queue' do 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(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } let(:synchronizer) do telemetry_api = SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) impressions_api = SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) @@ -104,12 +105,12 @@ context 'kill split notification' do 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(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:synchronizer) do telemetry_api = SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) impressions_api = SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) @@ -176,12 +177,12 @@ context 'update with flagset filter' do let(:flag_sets_repository) {SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new(["set_1"])} let(:flag_set_filter) {SplitIoClient::Cache::Filter::FlagSetsFilter.new(["set_1"])} - let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:synchronizer) do telemetry_api = SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) impressions_api = SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) @@ -230,12 +231,12 @@ context 'instant ff update split notification' do 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(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:split_fetcher) { SplitIoClient::Cache::Fetchers::SplitFetcher.new(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer) } let(:segment_fetcher) { SplitIoClient::Cache::Fetchers::SegmentFetcher.new(segments_repository, api_key, config, telemetry_runtime_producer) } - let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } - let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } + let(:rule_based_segments_repository) { SplitIoClient::Cache::Repositories::RuleBasedSegmentsRepository.new(config, events_queue) } let(:synchronizer) do telemetry_api = SplitIoClient::Api::TelemetryApi.new(config, api_key, telemetry_runtime_producer) impressions_api = SplitIoClient::Api::Impressions.new(api_key, config, telemetry_runtime_producer) diff --git a/spec/telemetry/synchronizer_spec.rb b/spec/telemetry/synchronizer_spec.rb index 5448027c..3f005bb1 100644 --- a/spec/telemetry/synchronizer_spec.rb +++ b/spec/telemetry/synchronizer_spec.rb @@ -30,14 +30,15 @@ end context 'Memory' do + let(:events_queue) { Queue.new } let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } let(:evaluation_consumer) { SplitIoClient::Telemetry::EvaluationConsumer.new(config) } let(:init_consumer) { SplitIoClient::Telemetry::InitConsumer.new(config) } let(:runtime_consumer) { SplitIoClient::Telemetry::RuntimeConsumer.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(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config) } + let(:splits_repository) { SplitIoClient::Cache::Repositories::SplitsRepository.new(config, flag_sets_repository, flag_set_filter, events_queue) } + let(:segments_repository) { SplitIoClient::Cache::Repositories::SegmentsRepository.new(config, events_queue) } let(:api_key) { 'Synchronizer-key' } let(:runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:evaluation_producer) { SplitIoClient::Telemetry::EvaluationProducer.new(config) } From 4dd96866ac1c0b4df804f3d69e3607b5042e02b6 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 11 Mar 2026 10:14:56 -0700 Subject: [PATCH 39/46] - fixed multiple events fire with segment update - fixed logging debug if config debug enabled --- .../cache/repositories/segments_repository.rb | 17 ++++++------ lib/splitclient-rb/clients/split_client.rb | 4 +-- lib/splitclient-rb/engine/api/splits.rb | 4 +-- lib/splitclient-rb/engine/auth_api_client.rb | 6 ++--- .../engine/events/events_manager.rb | 8 +++++- lib/splitclient-rb/engine/sync_manager.rb | 12 ++++----- lib/splitclient-rb/sse/event_source/client.rb | 27 +++++++++---------- .../sse/event_source/event_parser.rb | 4 +-- .../sse/notification_manager_keeper.rb | 6 ++--- .../sse/workers/segments_worker.rb | 8 +++--- .../sse/workers/splits_worker.rb | 10 +++---- lib/splitclient-rb/version.rb | 2 +- spec/splitclient/split_factory_spec.rb | 3 --- 13 files changed, 57 insertions(+), 54 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/segments_repository.rb b/lib/splitclient-rb/cache/repositories/segments_repository.rb index 900da206..7115d1d3 100644 --- a/lib/splitclient-rb/cache/repositories/segments_repository.rb +++ b/lib/splitclient-rb/cache/repositories/segments_repository.rb @@ -23,18 +23,19 @@ def add_to_segment(segment) name = segment[:name] @adapter.initialize_set(segment_data(name)) unless @adapter.exists?(segment_data(name)) - add_keys(name, segment[:added]) remove_keys(name, segment[:removed]) - @internal_events_queue.push( - SplitIoClient::Engine::Models::SdkInternalEventNotification.new( - SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED, - SplitIoClient::Engine::Models::EventsMetadata.new( - SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE, - [] + if segment[:added].length > 0 || segment[:removed].length > 0 + @internal_events_queue.push( + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE, + [] + ) ) ) - ) + end end def get_segment_keys(name) diff --git a/lib/splitclient-rb/clients/split_client.rb b/lib/splitclient-rb/clients/split_client.rb index c4abe7eb..c350b719 100644 --- a/lib/splitclient-rb/clients/split_client.rb +++ b/lib/splitclient-rb/clients/split_client.rb @@ -118,11 +118,11 @@ def destroy @config.logger.info('Split client shutdown started...') if @config.debug_enabled 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") + @config.logger.debug("Impressions and/or Events cache is not empty") if @config.debug_enabled # 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.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") if @config.debug_enabled @config.threads[:start_sdk].join(5) if @config.threads.key?(:start_sdk) end end diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index 887adb26..6ecd1473 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -24,7 +24,7 @@ def since(since, since_rbs, fetch_options = { cache_control_headers: false, till if check_last_proxy_check_timestamp @spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION - @config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.") + @config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.") if @config.debug_enabled @old_spec_since = since since = -1 since_rbs = -1 @@ -41,7 +41,7 @@ def since(since, since_rbs, fetch_options = { cache_control_headers: false, till params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty? params[:till] = fetch_options[:till] unless fetch_options[:till].nil? - @config.logger.debug("Fetching from splitChanges with #{params}: ") + @config.logger.debug("Fetching from splitChanges with #{params}: ") if @config.debug_enabled response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers]) if response.status == 414 @config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.") diff --git a/lib/splitclient-rb/engine/auth_api_client.rb b/lib/splitclient-rb/engine/auth_api_client.rb index 475842a2..d35e99e9 100644 --- a/lib/splitclient-rb/engine/auth_api_client.rb +++ b/lib/splitclient-rb/engine/auth_api_client.rb @@ -21,10 +21,10 @@ def authenticate(api_key) return process_error(response) if response.status >= 400 && response.status < 500 @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TOKEN_SYNC, response.status.to_i) - @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") + @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") if @config.debug_enabled { push_enabled: false, retry: true } rescue StandardError => e - @config.logger.debug("AuthApiClient error: #{e.inspect}.") + @config.logger.debug("AuthApiClient error: #{e.inspect}.") if @config.debug_enabled { push_enabled: false, retry: false } end @@ -51,7 +51,7 @@ def decode_token(token) end def process_error(response) - @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") + @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") if @config.debug_enabled @telemetry_runtime_producer.record_auth_rejections if response.status == 401 { push_enabled: false, retry: false } diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb index c2d687f0..f488ccd2 100644 --- a/lib/splitclient-rb/engine/events/events_manager.rb +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -48,7 +48,7 @@ def notify_internal_event(sdk_internal_event, event_metadata) end # if client is not subscribed to SDK_READY - if sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && get_event_handler(sorted_event).nil? + if check_if_register_needed(sorted_event) @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil) end @@ -65,6 +65,12 @@ def destroy private + def check_if_register_needed(sorted_event) + sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && + get_event_handler(sorted_event).nil? && + !@active_subscriptions.include?(sorted_event) + end + def fire_sdk_event(sdk_event, event_metadata) @config.logger.debug("EventsManager: Firing Sdk event: #{sdk_event}") if @config.debug_enabled @config.threads[:sdk_event_notify] = Thread.new do diff --git a/lib/splitclient-rb/engine/sync_manager.rb b/lib/splitclient-rb/engine/sync_manager.rb index d1f35c66..ff8de241 100644 --- a/lib/splitclient-rb/engine/sync_manager.rb +++ b/lib/splitclient-rb/engine/sync_manager.rb @@ -47,13 +47,13 @@ def start_thread connected = false if @config.streaming_enabled - @config.logger.debug('Starting Streaming mode ...') + @config.logger.debug('Starting Streaming mode ...') if @config.debug_enabled start_push_status_monitor connected = @push_manager.start_sse end unless connected - @config.logger.debug('Starting Polling mode ...') + @config.logger.debug('Starting Polling mode ...') if @config.debug_enabled @synchronizer.start_periodic_fetch record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING) end @@ -92,7 +92,7 @@ def process_push_shutdown def process_connected if @sse_connected.value - @config.logger.debug('Streaming already connected.') + @config.logger.debug('Streaming already connected.') if @config.debug_enabled return end @@ -107,7 +107,7 @@ def process_connected def process_forced_stop unless @sse_connected.value - @config.logger.debug('Streaming already disconnected.') + @config.logger.debug('Streaming already disconnected.') if @config.debug_enabled return end @@ -120,7 +120,7 @@ def process_forced_stop def process_disconnect(reconnect) unless @sse_connected.value - @config.logger.debug('Streaming already disconnected.') + @config.logger.debug('Streaming already disconnected.') if @config.debug_enabled return end @@ -169,7 +169,7 @@ def incoming_push_status_handler when Constants::PUSH_SUBSYSTEM_OFF process_push_shutdown else - @config.logger.debug('Incorrect push status type.') + @config.logger.debug('Incorrect push status type.') if @config.debug_enabled end end rescue StandardError => e diff --git a/lib/splitclient-rb/sse/event_source/client.rb b/lib/splitclient-rb/sse/event_source/client.rb index bb07d76a..5190ad6e 100644 --- a/lib/splitclient-rb/sse/event_source/client.rb +++ b/lib/splitclient-rb/sse/event_source/client.rb @@ -38,23 +38,23 @@ def initialize(config, def close(status = nil) unless connected? - @config.logger.debug('SSEClient already disconected.') + @config.logger.debug('SSEClient already disconected.') if @config.debug_enabled return end - @config.logger.debug("Closing SSEClient socket") + @config.logger.debug("Closing SSEClient socket") if @config.debug_enabled push_status(status) @connected.make_false @socket.sync_close = true if @socket.is_a? OpenSSL::SSL::SSLSocket @socket.close - @config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket + @config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket && @config.debug_enabled rescue StandardError => e @config.logger.error("SSEClient close Error: #{e.inspect}") end def start(url) if connected? - @config.logger.debug('SSEClient already running.') + @config.logger.debug('SSEClient already running.') if @config.debug_enabled return true end @@ -96,18 +96,17 @@ def connect_stream(latch) raise 'eof exception' if partial_data == :eof rescue IO::WaitReadable => e - @config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") + @config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") if @config.debug_enabled IO.select([@socket], nil, nil, @read_timeout) retry rescue Errno::EAGAIN => e - @config.logger.debug("SSE client transient error: #{e.inspect}") + @config.logger.debug("SSE client transient error: #{e.inspect}") if @config.debug_enabled IO.select([@socket], nil, nil, @read_timeout) retry rescue Errno::ETIMEDOUT => e @config.logger.error("SSE read operation timed out!: #{e.inspect}") return Constants::PUSH_RETRYABLE_ERROR rescue EOFError => e - puts "SSE read operation EOF Exception!: #{e.inspect}" @config.logger.error("SSE read operation EOF Exception!: #{e.inspect}") raise 'eof exception' rescue Errno::EBADF, IOError => e @@ -125,12 +124,12 @@ def connect_stream(latch) return Constants::PUSH_RETRYABLE_ERROR end rescue Errno::EBADF - @config.logger.debug("SSE socket is not connected (Errno::EBADF)") + @config.logger.debug("SSE socket is not connected (Errno::EBADF)") if @config.debug_enabled break rescue RuntimeError raise 'eof exception' rescue Exception => e - @config.logger.debug("SSE socket is not connected: #{e.inspect}") + @config.logger.debug("SSE socket is not connected: #{e.inspect}") if @config.debug_enabled break end @@ -156,7 +155,7 @@ def read_first_event(data, latch) return unless @first_event.value response_code = @event_parser.first_event(data) - @config.logger.debug("SSE client first event code: #{response_code}") + @config.logger.debug("SSE client first event code: #{response_code}") if @config.debug_enabled error_event = false events = @event_parser.parse(data) @@ -165,7 +164,7 @@ def read_first_event(data, latch) if response_code == OK_CODE && !error_event @connected.make_true - @config.logger.debug("SSE client first event Connected is true") + @config.logger.debug("SSE client first event Connected is true") if @config.debug_enabled @telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::SSE_CONNECTION_ESTABLISHED, nil) push_status(Constants::PUSH_CONNECTED) end @@ -202,7 +201,7 @@ def socket_connect end def process_data(partial_data) - @config.logger.debug("Event partial data: #{partial_data}") + @config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE events = @event_parser.parse(partial_data) @@ -220,7 +219,7 @@ def build_request(uri) req << "SplitSDKMachineName: #{@config.machine_name}\r\n" req << "SplitSDKClientKey: #{@api_key.split(//).last(4).join}\r\n" unless @api_key.nil? req << "Cache-Control: no-cache\r\n\r\n" - @config.logger.debug("Request info: #{req}") + @config.logger.debug("Request info: #{req}") if @config.debug_enabled req end @@ -255,7 +254,7 @@ def dispatch_event(event) def push_status(status) return if status.nil? - @config.logger.debug("Pushing new sse status: #{status}") + @config.logger.debug("Pushing new sse status: #{status}") if @config.debug_enabled @status_queue.push(status) end end diff --git a/lib/splitclient-rb/sse/event_source/event_parser.rb b/lib/splitclient-rb/sse/event_source/event_parser.rb index 566cb5c2..8983c9d7 100644 --- a/lib/splitclient-rb/sse/event_source/event_parser.rb +++ b/lib/splitclient-rb/sse/event_source/event_parser.rb @@ -29,14 +29,14 @@ def parse(raw_event) events rescue StandardError => e - @config.logger.debug("Error during parsing a event: #{e.inspect}") + @config.logger.debug("Error during parsing a event: #{e.inspect}") if @config.debug_enabled [] end def first_event(raw_data) raw_data.split("\n")[0].split(' ')[1].to_i rescue StandardError => e - @config.logger.debug("Error parsing first event: #{e.inspect}") + @config.logger.error("Error parsing first event: #{e.inspect}") BAD_REQUEST_CODE end diff --git a/lib/splitclient-rb/sse/notification_manager_keeper.rb b/lib/splitclient-rb/sse/notification_manager_keeper.rb index 33ff2b96..34b24581 100644 --- a/lib/splitclient-rb/sse/notification_manager_keeper.rb +++ b/lib/splitclient-rb/sse/notification_manager_keeper.rb @@ -42,12 +42,12 @@ def process_event_control(type) @telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::STREAMING_STATUS, DISABLED) push_status(Constants::PUSH_SUBSYSTEM_OFF) else - @config.logger.error("Incorrect event type: #{incoming_notification}") + @config.logger.error("Incorrect event type: #{incoming_notification}") if @config.debug_enabled end end def process_event_occupancy(channel, publishers) - @config.logger.debug("Processed occupancy event with #{publishers} publishers. Channel: #{channel}") + @config.logger.debug("Processed occupancy event with #{publishers} publishers. Channel: #{channel}") if @config.debug_enabled update_publishers(channel, publishers) @@ -76,7 +76,7 @@ def are_publishers_available? end def push_status(status) - @config.logger.debug("Pushing occupancy status: #{status}") + @config.logger.debug("Pushing occupancy status: #{status}") if @config.debug_enabled @status_queue.push(status) end end diff --git a/lib/splitclient-rb/sse/workers/segments_worker.rb b/lib/splitclient-rb/sse/workers/segments_worker.rb index a241c72d..828e7c2d 100644 --- a/lib/splitclient-rb/sse/workers/segments_worker.rb +++ b/lib/splitclient-rb/sse/workers/segments_worker.rb @@ -14,13 +14,13 @@ def initialize(synchronizer, config, segments_repository) def add_to_queue(change_number, segment_name) item = { change_number: change_number, segment_name: segment_name } - @config.logger.debug("SegmentsWorker add to queue #{item}") + @config.logger.debug("SegmentsWorker add to queue #{item}") if @config.debug_enabled @queue.push(item) end def start if @running.value - @config.logger.debug('segments worker already running.') + @config.logger.debug('segments worker already running.') if @config.debug_enabled return end @@ -30,7 +30,7 @@ def start def stop unless @running.value - @config.logger.debug('segments worker not running.') + @config.logger.debug('segments worker not running.') if @config.debug_enabled return end @@ -44,7 +44,7 @@ def perform while (item = @queue.pop) segment_name = item[:segment_name] cn = item[:change_number] - @config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}") + @config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}") if @config.debug_enabled @synchronizer.fetch_segment(segment_name, cn) end diff --git a/lib/splitclient-rb/sse/workers/splits_worker.rb b/lib/splitclient-rb/sse/workers/splits_worker.rb index dc15eb2c..ab873f85 100644 --- a/lib/splitclient-rb/sse/workers/splits_worker.rb +++ b/lib/splitclient-rb/sse/workers/splits_worker.rb @@ -18,7 +18,7 @@ def initialize(synchronizer, config, feature_flags_repository, telemetry_runtime def start if @running.value - @config.logger.debug('feature_flags_worker already running.') + @config.logger.debug('feature_flags_worker already running.') if @config.debug_enabled return end @@ -28,7 +28,7 @@ def start def stop unless @running.value - @config.logger.debug('feature_flags_worker not running.') + @config.logger.debug('feature_flags_worker not running.') if @config.debug_enabled return end @@ -37,7 +37,7 @@ def stop end def add_to_queue(notification) - @config.logger.debug("feature_flags_worker add to queue #{notification.data['changeNumber']}") + @config.logger.debug("feature_flags_worker add to queue #{notification.data['changeNumber']}") if @config.debug_enabled @queue.push(notification) end @@ -52,7 +52,7 @@ def perform_thread def perform while (notification = @queue.pop) - @config.logger.debug("feature_flags_worker change_number dequeue #{notification.data['changeNumber']}") + @config.logger.debug("feature_flags_worker change_number dequeue #{notification.data['changeNumber']}") if @config.debug_enabled case notification.data['type'] when SSE::EventSource::EventTypes::SPLIT_UPDATE success = update_feature_flag(notification) @@ -117,7 +117,7 @@ def update_rule_based_segment(notification) def kill_feature_flag(notification) return if @feature_flags_repository.get_change_number.to_i > notification.data['changeNumber'] - @config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}") + @config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}") if @config.debug_enabled @feature_flags_repository.kill(notification.data['changeNumber'], notification.data['splitName'], notification.data['defaultTreatment']) diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index 5b66f95f..ba58bfec 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.11.0' + VERSION = '8.11.0-rc2' end diff --git a/spec/splitclient/split_factory_spec.rb b/spec/splitclient/split_factory_spec.rb index dd0ef73a..42af6904 100644 --- a/spec/splitclient/split_factory_spec.rb +++ b/spec/splitclient/split_factory_spec.rb @@ -129,9 +129,6 @@ expect(factory.instance_variable_get(:@config).valid_mode).to be false expect(factory.manager.split('test_split')) .to be nil - - puts '###### log' - puts log.string end end From 455e84db73033fa58263028c83e971f9a89f68ac Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 11 Mar 2026 10:26:48 -0700 Subject: [PATCH 40/46] polishing --- lib/splitclient-rb/engine/auth_api_client.rb | 8 ++++++-- lib/splitclient-rb/engine/sync_manager.rb | 20 +++++++++++-------- .../sse/workers/splits_worker.rb | 8 ++++++-- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/lib/splitclient-rb/engine/auth_api_client.rb b/lib/splitclient-rb/engine/auth_api_client.rb index d35e99e9..f3b7c5c6 100644 --- a/lib/splitclient-rb/engine/auth_api_client.rb +++ b/lib/splitclient-rb/engine/auth_api_client.rb @@ -21,7 +21,9 @@ def authenticate(api_key) return process_error(response) if response.status >= 400 && response.status < 500 @telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TOKEN_SYNC, response.status.to_i) - @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") if @config.debug_enabled + if @config.debug_enabled + @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") + end { push_enabled: false, retry: true } rescue StandardError => e @config.logger.debug("AuthApiClient error: #{e.inspect}.") if @config.debug_enabled @@ -51,7 +53,9 @@ def decode_token(token) end def process_error(response) - @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") if @config.debug_enabled + if @config.debug_enabled + @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") + end @telemetry_runtime_producer.record_auth_rejections if response.status == 401 { push_enabled: false, retry: false } diff --git a/lib/splitclient-rb/engine/sync_manager.rb b/lib/splitclient-rb/engine/sync_manager.rb index ff8de241..0deb9b24 100644 --- a/lib/splitclient-rb/engine/sync_manager.rb +++ b/lib/splitclient-rb/engine/sync_manager.rb @@ -47,13 +47,13 @@ def start_thread connected = false if @config.streaming_enabled - @config.logger.debug('Starting Streaming mode ...') if @config.debug_enabled + log_if_debug('Starting Streaming mode ...') start_push_status_monitor connected = @push_manager.start_sse end unless connected - @config.logger.debug('Starting Polling mode ...') if @config.debug_enabled + log_if_debug('Starting Polling mode ...') @synchronizer.start_periodic_fetch record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING) end @@ -92,7 +92,7 @@ def process_push_shutdown def process_connected if @sse_connected.value - @config.logger.debug('Streaming already connected.') if @config.debug_enabled + log_if_debug('Streaming already connected.') return end @@ -107,7 +107,7 @@ def process_connected def process_forced_stop unless @sse_connected.value - @config.logger.debug('Streaming already disconnected.') if @config.debug_enabled + log_if_debug('Streaming already disconnected.') return end @@ -120,7 +120,7 @@ def process_forced_stop def process_disconnect(reconnect) unless @sse_connected.value - @config.logger.debug('Streaming already disconnected.') if @config.debug_enabled + log_if_debug('Streaming already disconnected.') return end @@ -144,14 +144,14 @@ def record_telemetry(type, data) def start_push_status_monitor @config.threads[:push_status_handler] = Thread.new do - @config.logger.debug('Starting push status handler ...') if @config.debug_enabled + log_if_debug('Starting push status handler ...') incoming_push_status_handler end end def incoming_push_status_handler while (status = @status_queue.pop) - @config.logger.debug("Push status handler dequeue #{status}") if @config.debug_enabled + log_if_debug("Push status handler dequeue #{status}") case status when Constants::PUSH_CONNECTED @@ -169,12 +169,16 @@ def incoming_push_status_handler when Constants::PUSH_SUBSYSTEM_OFF process_push_shutdown else - @config.logger.debug('Incorrect push status type.') if @config.debug_enabled + log_if_debug('Incorrect push status type.') end end rescue StandardError => e @config.logger.error("Push status handler error: #{e.inspect}") end end + + def log_if_debug(msg) + @config.logger.debug(msg) if @config.debug_enabled + end end end diff --git a/lib/splitclient-rb/sse/workers/splits_worker.rb b/lib/splitclient-rb/sse/workers/splits_worker.rb index ab873f85..7fffc1f0 100644 --- a/lib/splitclient-rb/sse/workers/splits_worker.rb +++ b/lib/splitclient-rb/sse/workers/splits_worker.rb @@ -52,7 +52,9 @@ def perform_thread def perform while (notification = @queue.pop) - @config.logger.debug("feature_flags_worker change_number dequeue #{notification.data['changeNumber']}") if @config.debug_enabled + if @config.debug_enabled + @config.logger.debug("feature_flags_worker change_number dequeue #{notification.data['changeNumber']}") + end case notification.data['type'] when SSE::EventSource::EventTypes::SPLIT_UPDATE success = update_feature_flag(notification) @@ -117,7 +119,9 @@ def update_rule_based_segment(notification) def kill_feature_flag(notification) return if @feature_flags_repository.get_change_number.to_i > notification.data['changeNumber'] - @config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}") if @config.debug_enabled + if @config.debug_enabled + @config.logger.debug("feature_flags_worker kill #{notification.data['splitName']}, #{notification.data['changeNumber']}") + end @feature_flags_repository.kill(notification.data['changeNumber'], notification.data['splitName'], notification.data['defaultTreatment']) From 55fa95e9ce16f4978fc0037ebc61b2f0aaa48f90 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 11 Mar 2026 11:48:01 -0700 Subject: [PATCH 41/46] used relative load for events models classes --- .../rule_based_segments_repository.rb | 12 +++++++---- .../cache/repositories/segments_repository.rb | 13 ++++++++---- .../cache/repositories/splits_repository.rb | 20 +++++++++++-------- .../engine/events/events_manager.rb | 19 +++++++++--------- .../models/sdk_internal_event_notification.rb | 14 +++++++------ lib/splitclient-rb/engine/status_manager.rb | 6 ++++-- lib/splitclient-rb/engine/sync_manager.rb | 14 ++++++------- spec/sse/event_source/client_spec.rb | 4 ++-- 8 files changed, 60 insertions(+), 42 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb b/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb index 67f339fe..1b35c6d5 100644 --- a/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +++ b/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb @@ -1,4 +1,8 @@ require 'concurrent' +require_relative '../../engine/models/sdk_internal_event_notification.rb' +require_relative '../../engine/models/events_metadata.rb' +require_relative '../../engine/models/sdk_internal_event.rb' +require_relative '../../engine/models/sdk_event_type.rb' module SplitIoClient module Cache @@ -50,10 +54,10 @@ def update(to_add, to_delete, new_change_number) if to_add.length > 0 || to_delete.length > 0 @internal_events_queue.push( - SplitIoClient::Engine::Models::SdkInternalEventNotification.new( - SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED, - SplitIoClient::Engine::Models::EventsMetadata.new( - SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE, + SdkInternalEventNotification.new( + SdkInternalEvent::RB_SEGMENTS_UPDATED, + EventsMetadata.new( + SdkEventType::SEGMENTS_UPDATE, [] ) ) diff --git a/lib/splitclient-rb/cache/repositories/segments_repository.rb b/lib/splitclient-rb/cache/repositories/segments_repository.rb index 7115d1d3..39cc3f53 100644 --- a/lib/splitclient-rb/cache/repositories/segments_repository.rb +++ b/lib/splitclient-rb/cache/repositories/segments_repository.rb @@ -1,3 +1,8 @@ +require_relative '../../engine/models/sdk_internal_event_notification.rb' +require_relative '../../engine/models/events_metadata.rb' +require_relative '../../engine/models/sdk_internal_event.rb' +require_relative '../../engine/models/sdk_event_type.rb' + module SplitIoClient module Cache module Repositories @@ -27,10 +32,10 @@ def add_to_segment(segment) remove_keys(name, segment[:removed]) if segment[:added].length > 0 || segment[:removed].length > 0 @internal_events_queue.push( - SplitIoClient::Engine::Models::SdkInternalEventNotification.new( - SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED, - SplitIoClient::Engine::Models::EventsMetadata.new( - SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE, + SdkInternalEventNotification.new( + SdkInternalEvent::SEGMENTS_UPDATED, + EventsMetadata.new( + SdkEventType::SEGMENTS_UPDATE, [] ) ) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index f9a8acbb..496f7496 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -1,4 +1,8 @@ require 'concurrent' +require_relative '../../engine/models/sdk_internal_event_notification.rb' +require_relative '../../engine/models/events_metadata.rb' +require_relative '../../engine/models/sdk_internal_event.rb' +require_relative '../../engine/models/sdk_event_type.rb' module SplitIoClient module Cache @@ -57,10 +61,10 @@ def update(to_add, to_delete, new_change_number) if to_add.length > 0 || to_delete.length > 0 @internal_events_queue.push( - SplitIoClient::Engine::Models::SdkInternalEventNotification.new( - SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, - SplitIoClient::Engine::Models::EventsMetadata.new( - SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, + SdkInternalEventNotification.new( + SdkInternalEvent::FLAGS_UPDATED, + EventsMetadata.new( + SdkEventType::FLAG_UPDATE, to_add.map {|flag| flag[:name]} | to_delete.map {|flag| flag[:name]} ) ) @@ -154,10 +158,10 @@ def kill(change_number, split_name, default_treatment) @adapter.set_string(namespace_key(".split.#{split_name}"), split.to_json) @internal_events_queue.push( - SplitIoClient::Engine::Models::SdkInternalEventNotification.new( - SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION, - SplitIoClient::Engine::Models::EventsMetadata.new( - SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, + SdkInternalEventNotification.new( + SdkInternalEvent::FLAG_KILLED_NOTIFICATION, + EventsMetadata.new( + SdkEventType::FLAG_UPDATE, [split_name] ) ) diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb index f488ccd2..86d83eb6 100644 --- a/lib/splitclient-rb/engine/events/events_manager.rb +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +include SplitIoClient::Engine::Models module SplitIoClient module Engine @@ -18,15 +19,15 @@ def register(sdk_event, event_handler) @mutex.synchronize do # SDK ready already fired - if sdk_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event) - @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, event_handler) + if sdk_event == SdkEvent::SDK_READY && event_already_triggered(sdk_event) + @active_subscriptions[sdk_event] = EventActiveSubscriptions.new(true, event_handler) @config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled fire_sdk_event(sdk_event, nil) return end @config.logger.debug("EventsManager: Register event: #{sdk_event}") if @config.debug_enabled - @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(false, event_handler) + @active_subscriptions[sdk_event] = EventActiveSubscriptions.new(false, event_handler) end end @@ -50,7 +51,7 @@ def notify_internal_event(sdk_internal_event, event_metadata) # if client is not subscribed to SDK_READY if check_if_register_needed(sorted_event) @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled - @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil) + @active_subscriptions[SdkEvent::SDK_READY] = EventActiveSubscriptions.new(true, nil) end end end @@ -66,7 +67,7 @@ def destroy private def check_if_register_needed(sorted_event) - sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && + sorted_event == SdkEvent::SDK_READY && get_event_handler(sorted_event).nil? && !@active_subscriptions.include?(sorted_event) end @@ -110,7 +111,7 @@ def get_event_handler(sdk_event) end def get_sdk_event_if_applicable(sdk_internal_event) - final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false) + final_sdk_event = ValidSdkEvent.new(nil, false) events_to_fire = [] require_any_sdk_event = check_require_any(sdk_internal_event) @@ -118,7 +119,7 @@ def get_sdk_event_if_applicable(sdk_internal_event) if (!event_already_triggered(require_any_sdk_event.sdk_event) && execution_limit(require_any_sdk_event.sdk_event) == 1) || execution_limit(require_any_sdk_event.sdk_event) == -1 - final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new( + final_sdk_event = ValidSdkEvent.new( require_any_sdk_event.sdk_event, check_prerequisites(require_any_sdk_event.sdk_event) && check_suppressed_by(require_any_sdk_event.sdk_event) @@ -178,10 +179,10 @@ def execution_limit(sdk_event) end def check_require_any(sdk_internal_event) - valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false) + valid_sdk_event = ValidSdkEvent.new(nil, false) @manager_config.require_any.each do |name, val| if val.include?(sdk_internal_event) - valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true) + valid_sdk_event = ValidSdkEvent.new(name, true) return valid_sdk_event end end diff --git a/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb b/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb index e202c707..b88f60d4 100644 --- a/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +++ b/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb @@ -1,13 +1,15 @@ # frozen_string_literal: false module SplitIoClient - module Engine::Models - class SdkInternalEventNotification - attr_reader :internal_event, :metadata + module Engine + module Models + class SdkInternalEventNotification + attr_reader :internal_event, :metadata - def initialize(internal_event, metadata) - @internal_event = internal_event - @metadata = metadata + def initialize(internal_event, metadata) + @internal_event = internal_event + @metadata = metadata + end end end end diff --git a/lib/splitclient-rb/engine/status_manager.rb b/lib/splitclient-rb/engine/status_manager.rb index e5ed19d1..85ad2667 100644 --- a/lib/splitclient-rb/engine/status_manager.rb +++ b/lib/splitclient-rb/engine/status_manager.rb @@ -1,4 +1,6 @@ # frozen_string_literal: true +require_relative './models/sdk_internal_event_notification.rb' +require_relative './models/sdk_internal_event.rb' module SplitIoClient module Engine @@ -21,8 +23,8 @@ def ready! @sdk_ready.count_down @config.logger.info('SplitIO SDK is ready') @internal_events_queue.push( - SplitIoClient::Engine::Models::SdkInternalEventNotification.new( - SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil + SdkInternalEventNotification.new( + SdkInternalEvent::SDK_READY, nil ) ) end diff --git a/lib/splitclient-rb/engine/sync_manager.rb b/lib/splitclient-rb/engine/sync_manager.rb index 0deb9b24..8bb78b4b 100644 --- a/lib/splitclient-rb/engine/sync_manager.rb +++ b/lib/splitclient-rb/engine/sync_manager.rb @@ -47,13 +47,13 @@ def start_thread connected = false if @config.streaming_enabled - log_if_debug('Starting Streaming mode ...') + @config.logger.debug('Starting Streaming mode ...') if @config.debug_enabled start_push_status_monitor connected = @push_manager.start_sse end unless connected - log_if_debug('Starting Polling mode ...') + @config.logger.debug('Starting Polling mode ...') if @config.debug_enabled @synchronizer.start_periodic_fetch record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING) end @@ -92,7 +92,7 @@ def process_push_shutdown def process_connected if @sse_connected.value - log_if_debug('Streaming already connected.') + @config.logger.debug('Streaming already connected.') if @config.debug_enabled return end @@ -107,7 +107,7 @@ def process_connected def process_forced_stop unless @sse_connected.value - log_if_debug('Streaming already disconnected.') + @config.logger.debug('Streaming already disconnected.') if @config.debug_enabled return end @@ -120,7 +120,7 @@ def process_forced_stop def process_disconnect(reconnect) unless @sse_connected.value - log_if_debug('Streaming already disconnected.') + @config.logger.debug('Streaming already disconnected.') if @config.debug_enabled return end @@ -144,14 +144,14 @@ def record_telemetry(type, data) def start_push_status_monitor @config.threads[:push_status_handler] = Thread.new do - log_if_debug('Starting push status handler ...') + @config.logger.debug('Starting push status handler ...') if @config.debug_enabled incoming_push_status_handler end end def incoming_push_status_handler while (status = @status_queue.pop) - log_if_debug("Push status handler dequeue #{status}") + @config.logger.debug("Push status handler dequeue #{status}") if @config.debug_enabled case status when Constants::PUSH_CONNECTED diff --git a/spec/sse/event_source/client_spec.rb b/spec/sse/event_source/client_spec.rb index 909cb6f5..ce4cccdd 100644 --- a/spec/sse/event_source/client_spec.rb +++ b/spec/sse/event_source/client_spec.rb @@ -9,7 +9,7 @@ let(:log) { StringIO.new } let(:events_queue) { Queue.new } - let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log)) } + let(:config) { SplitIoClient::SplitConfig.new(logger: Logger.new(log), debug_enabled: true) } let(:telemetry_runtime_producer) { SplitIoClient::Telemetry::RuntimeProducer.new(config) } let(:telemetry_runtime_consumer) { SplitIoClient::Telemetry::RuntimeConsumer.new(config) } let(:api_token) { 'api-token-test' } @@ -332,7 +332,7 @@ it 'test retry with IO::WaitReadable exceptions' do log2 = StringIO.new - config2 = SplitIoClient::SplitConfig.new(logger: Logger.new(log2)) + config2 = SplitIoClient::SplitConfig.new(logger: Logger.new(log2), debug_enabled: true) mock_server do |server| server.setup_response('/') do |_, res| From b289f74566c32ccc0608c37de7e06aafbb5376ae Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 11 Mar 2026 13:13:00 -0700 Subject: [PATCH 42/46] fixed sync_manager errors --- .../rule_based_segments_repository.rb | 12 ++++------- .../cache/repositories/segments_repository.rb | 13 ++++-------- .../cache/repositories/splits_repository.rb | 20 ++++++++----------- .../engine/events/events_manager.rb | 19 +++++++++--------- lib/splitclient-rb/engine/status_manager.rb | 6 ++---- 5 files changed, 27 insertions(+), 43 deletions(-) diff --git a/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb b/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb index 1b35c6d5..67f339fe 100644 --- a/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +++ b/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb @@ -1,8 +1,4 @@ require 'concurrent' -require_relative '../../engine/models/sdk_internal_event_notification.rb' -require_relative '../../engine/models/events_metadata.rb' -require_relative '../../engine/models/sdk_internal_event.rb' -require_relative '../../engine/models/sdk_event_type.rb' module SplitIoClient module Cache @@ -54,10 +50,10 @@ def update(to_add, to_delete, new_change_number) if to_add.length > 0 || to_delete.length > 0 @internal_events_queue.push( - SdkInternalEventNotification.new( - SdkInternalEvent::RB_SEGMENTS_UPDATED, - EventsMetadata.new( - SdkEventType::SEGMENTS_UPDATE, + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE, [] ) ) diff --git a/lib/splitclient-rb/cache/repositories/segments_repository.rb b/lib/splitclient-rb/cache/repositories/segments_repository.rb index 39cc3f53..7115d1d3 100644 --- a/lib/splitclient-rb/cache/repositories/segments_repository.rb +++ b/lib/splitclient-rb/cache/repositories/segments_repository.rb @@ -1,8 +1,3 @@ -require_relative '../../engine/models/sdk_internal_event_notification.rb' -require_relative '../../engine/models/events_metadata.rb' -require_relative '../../engine/models/sdk_internal_event.rb' -require_relative '../../engine/models/sdk_event_type.rb' - module SplitIoClient module Cache module Repositories @@ -32,10 +27,10 @@ def add_to_segment(segment) remove_keys(name, segment[:removed]) if segment[:added].length > 0 || segment[:removed].length > 0 @internal_events_queue.push( - SdkInternalEventNotification.new( - SdkInternalEvent::SEGMENTS_UPDATED, - EventsMetadata.new( - SdkEventType::SEGMENTS_UPDATE, + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE, [] ) ) diff --git a/lib/splitclient-rb/cache/repositories/splits_repository.rb b/lib/splitclient-rb/cache/repositories/splits_repository.rb index 496f7496..f9a8acbb 100644 --- a/lib/splitclient-rb/cache/repositories/splits_repository.rb +++ b/lib/splitclient-rb/cache/repositories/splits_repository.rb @@ -1,8 +1,4 @@ require 'concurrent' -require_relative '../../engine/models/sdk_internal_event_notification.rb' -require_relative '../../engine/models/events_metadata.rb' -require_relative '../../engine/models/sdk_internal_event.rb' -require_relative '../../engine/models/sdk_event_type.rb' module SplitIoClient module Cache @@ -61,10 +57,10 @@ def update(to_add, to_delete, new_change_number) if to_add.length > 0 || to_delete.length > 0 @internal_events_queue.push( - SdkInternalEventNotification.new( - SdkInternalEvent::FLAGS_UPDATED, - EventsMetadata.new( - SdkEventType::FLAG_UPDATE, + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, to_add.map {|flag| flag[:name]} | to_delete.map {|flag| flag[:name]} ) ) @@ -158,10 +154,10 @@ def kill(change_number, split_name, default_treatment) @adapter.set_string(namespace_key(".split.#{split_name}"), split.to_json) @internal_events_queue.push( - SdkInternalEventNotification.new( - SdkInternalEvent::FLAG_KILLED_NOTIFICATION, - EventsMetadata.new( - SdkEventType::FLAG_UPDATE, + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION, + SplitIoClient::Engine::Models::EventsMetadata.new( + SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE, [split_name] ) ) diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb index 86d83eb6..10273c75 100644 --- a/lib/splitclient-rb/engine/events/events_manager.rb +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -1,5 +1,4 @@ # frozen_string_literal: true -include SplitIoClient::Engine::Models module SplitIoClient module Engine @@ -19,15 +18,15 @@ def register(sdk_event, event_handler) @mutex.synchronize do # SDK ready already fired - if sdk_event == SdkEvent::SDK_READY && event_already_triggered(sdk_event) - @active_subscriptions[sdk_event] = EventActiveSubscriptions.new(true, event_handler) + if sdk_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event) + @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, event_handler) @config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled fire_sdk_event(sdk_event, nil) return end @config.logger.debug("EventsManager: Register event: #{sdk_event}") if @config.debug_enabled - @active_subscriptions[sdk_event] = EventActiveSubscriptions.new(false, event_handler) + @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(false, event_handler) end end @@ -51,7 +50,7 @@ def notify_internal_event(sdk_internal_event, event_metadata) # if client is not subscribed to SDK_READY if check_if_register_needed(sorted_event) @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled - @active_subscriptions[SdkEvent::SDK_READY] = EventActiveSubscriptions.new(true, nil) + @active_subscriptions[SplitIoClient::Engine::Models::SdkEvent::SDK_READY] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, nil) end end end @@ -67,7 +66,7 @@ def destroy private def check_if_register_needed(sorted_event) - sorted_event == SdkEvent::SDK_READY && + sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && get_event_handler(sorted_event).nil? && !@active_subscriptions.include?(sorted_event) end @@ -111,7 +110,7 @@ def get_event_handler(sdk_event) end def get_sdk_event_if_applicable(sdk_internal_event) - final_sdk_event = ValidSdkEvent.new(nil, false) + final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false) events_to_fire = [] require_any_sdk_event = check_require_any(sdk_internal_event) @@ -119,7 +118,7 @@ def get_sdk_event_if_applicable(sdk_internal_event) if (!event_already_triggered(require_any_sdk_event.sdk_event) && execution_limit(require_any_sdk_event.sdk_event) == 1) || execution_limit(require_any_sdk_event.sdk_event) == -1 - final_sdk_event = ValidSdkEvent.new( + final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new( require_any_sdk_event.sdk_event, check_prerequisites(require_any_sdk_event.sdk_event) && check_suppressed_by(require_any_sdk_event.sdk_event) @@ -179,10 +178,10 @@ def execution_limit(sdk_event) end def check_require_any(sdk_internal_event) - valid_sdk_event = ValidSdkEvent.new(nil, false) + valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false) @manager_config.require_any.each do |name, val| if val.include?(sdk_internal_event) - valid_sdk_event = ValidSdkEvent.new(name, true) + valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true) return valid_sdk_event end end diff --git a/lib/splitclient-rb/engine/status_manager.rb b/lib/splitclient-rb/engine/status_manager.rb index 85ad2667..e5ed19d1 100644 --- a/lib/splitclient-rb/engine/status_manager.rb +++ b/lib/splitclient-rb/engine/status_manager.rb @@ -1,6 +1,4 @@ # frozen_string_literal: true -require_relative './models/sdk_internal_event_notification.rb' -require_relative './models/sdk_internal_event.rb' module SplitIoClient module Engine @@ -23,8 +21,8 @@ def ready! @sdk_ready.count_down @config.logger.info('SplitIO SDK is ready') @internal_events_queue.push( - SdkInternalEventNotification.new( - SdkInternalEvent::SDK_READY, nil + SplitIoClient::Engine::Models::SdkInternalEventNotification.new( + SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil ) ) end From 8b2ccbba8f01a9629b3ef6892c36b403a54853c9 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 11 Mar 2026 13:19:55 -0700 Subject: [PATCH 43/46] polish --- .../engine/events/events_manager.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb index 10273c75..3cefd4f6 100644 --- a/lib/splitclient-rb/engine/events/events_manager.rb +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -18,15 +18,15 @@ def register(sdk_event, event_handler) @mutex.synchronize do # SDK ready already fired - if sdk_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event) - @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, event_handler) + if sdk_event == Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event) + @active_subscriptions[sdk_event] = Engine::Models::EventActiveSubscriptions.new(true, event_handler) @config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled fire_sdk_event(sdk_event, nil) return end @config.logger.debug("EventsManager: Register event: #{sdk_event}") if @config.debug_enabled - @active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(false, event_handler) + @active_subscriptions[sdk_event] = Engine::Models::EventActiveSubscriptions.new(false, event_handler) end end @@ -50,7 +50,7 @@ def notify_internal_event(sdk_internal_event, event_metadata) # if client is not subscribed to SDK_READY if check_if_register_needed(sorted_event) @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled - @active_subscriptions[SplitIoClient::Engine::Models::SdkEvent::SDK_READY] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, nil) + @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil) end end end @@ -66,7 +66,7 @@ def destroy private def check_if_register_needed(sorted_event) - sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && + sorted_event == Engine::Models::SdkEvent::SDK_READY && get_event_handler(sorted_event).nil? && !@active_subscriptions.include?(sorted_event) end @@ -110,7 +110,7 @@ def get_event_handler(sdk_event) end def get_sdk_event_if_applicable(sdk_internal_event) - final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false) + final_sdk_event = Engine::Models::ValidSdkEvent.new(nil, false) events_to_fire = [] require_any_sdk_event = check_require_any(sdk_internal_event) @@ -118,7 +118,7 @@ def get_sdk_event_if_applicable(sdk_internal_event) if (!event_already_triggered(require_any_sdk_event.sdk_event) && execution_limit(require_any_sdk_event.sdk_event) == 1) || execution_limit(require_any_sdk_event.sdk_event) == -1 - final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new( + final_sdk_event = Engine::Models::ValidSdkEvent.new( require_any_sdk_event.sdk_event, check_prerequisites(require_any_sdk_event.sdk_event) && check_suppressed_by(require_any_sdk_event.sdk_event) @@ -178,10 +178,10 @@ def execution_limit(sdk_event) end def check_require_any(sdk_internal_event) - valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false) + valid_sdk_event = Engine::Models::ValidSdkEvent.new(nil, false) @manager_config.require_any.each do |name, val| if val.include?(sdk_internal_event) - valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true) + valid_sdk_event = Engine::Models::ValidSdkEvent.new(name, true) return valid_sdk_event end end From df3f78eafb66561c6e44a14964659bfb36bdb314 Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 11 Mar 2026 14:22:40 -0700 Subject: [PATCH 44/46] polish --- .../engine/events/events_manager.rb | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb index 3cefd4f6..363a755e 100644 --- a/lib/splitclient-rb/engine/events/events_manager.rb +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -4,6 +4,8 @@ module SplitIoClient module Engine module Events class EventsManager + include SplitIoClient::Engine::Models + def initialize(events_manager_config, events_delivery, config) @manager_config = events_manager_config @events_delivery = events_delivery @@ -18,15 +20,15 @@ def register(sdk_event, event_handler) @mutex.synchronize do # SDK ready already fired - if sdk_event == Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event) - @active_subscriptions[sdk_event] = Engine::Models::EventActiveSubscriptions.new(true, event_handler) + if sdk_event == SdkEvent::SDK_READY && event_already_triggered(sdk_event) + @active_subscriptions[sdk_event] = EventActiveSubscriptions.new(true, event_handler) @config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled fire_sdk_event(sdk_event, nil) return end @config.logger.debug("EventsManager: Register event: #{sdk_event}") if @config.debug_enabled - @active_subscriptions[sdk_event] = Engine::Models::EventActiveSubscriptions.new(false, event_handler) + @active_subscriptions[sdk_event] = EventActiveSubscriptions.new(false, event_handler) end end @@ -50,7 +52,7 @@ def notify_internal_event(sdk_internal_event, event_metadata) # if client is not subscribed to SDK_READY if check_if_register_needed(sorted_event) @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled - @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil) + @active_subscriptions[SdkEvent::SDK_READY] = EventActiveSubscriptions.new(true, nil) end end end @@ -66,7 +68,7 @@ def destroy private def check_if_register_needed(sorted_event) - sorted_event == Engine::Models::SdkEvent::SDK_READY && + sorted_event == SdkEvent::SDK_READY && get_event_handler(sorted_event).nil? && !@active_subscriptions.include?(sorted_event) end @@ -110,7 +112,7 @@ def get_event_handler(sdk_event) end def get_sdk_event_if_applicable(sdk_internal_event) - final_sdk_event = Engine::Models::ValidSdkEvent.new(nil, false) + final_sdk_event = ValidSdkEvent.new(nil, false) events_to_fire = [] require_any_sdk_event = check_require_any(sdk_internal_event) @@ -118,7 +120,7 @@ def get_sdk_event_if_applicable(sdk_internal_event) if (!event_already_triggered(require_any_sdk_event.sdk_event) && execution_limit(require_any_sdk_event.sdk_event) == 1) || execution_limit(require_any_sdk_event.sdk_event) == -1 - final_sdk_event = Engine::Models::ValidSdkEvent.new( + final_sdk_event = ValidSdkEvent.new( require_any_sdk_event.sdk_event, check_prerequisites(require_any_sdk_event.sdk_event) && check_suppressed_by(require_any_sdk_event.sdk_event) @@ -178,10 +180,10 @@ def execution_limit(sdk_event) end def check_require_any(sdk_internal_event) - valid_sdk_event = Engine::Models::ValidSdkEvent.new(nil, false) + valid_sdk_event = ValidSdkEvent.new(nil, false) @manager_config.require_any.each do |name, val| if val.include?(sdk_internal_event) - valid_sdk_event = Engine::Models::ValidSdkEvent.new(name, true) + valid_sdk_event = ValidSdkEvent.new(name, true) return valid_sdk_event end end From 40fc8f03003ce4c3c4c90131dc1688c4f4e6a96b Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Wed, 11 Mar 2026 14:54:22 -0700 Subject: [PATCH 45/46] rolled back include in events manager --- .../engine/events/events_manager.rb | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/splitclient-rb/engine/events/events_manager.rb b/lib/splitclient-rb/engine/events/events_manager.rb index 363a755e..3cefd4f6 100644 --- a/lib/splitclient-rb/engine/events/events_manager.rb +++ b/lib/splitclient-rb/engine/events/events_manager.rb @@ -4,8 +4,6 @@ module SplitIoClient module Engine module Events class EventsManager - include SplitIoClient::Engine::Models - def initialize(events_manager_config, events_delivery, config) @manager_config = events_manager_config @events_delivery = events_delivery @@ -20,15 +18,15 @@ def register(sdk_event, event_handler) @mutex.synchronize do # SDK ready already fired - if sdk_event == SdkEvent::SDK_READY && event_already_triggered(sdk_event) - @active_subscriptions[sdk_event] = EventActiveSubscriptions.new(true, event_handler) + if sdk_event == Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event) + @active_subscriptions[sdk_event] = Engine::Models::EventActiveSubscriptions.new(true, event_handler) @config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled fire_sdk_event(sdk_event, nil) return end @config.logger.debug("EventsManager: Register event: #{sdk_event}") if @config.debug_enabled - @active_subscriptions[sdk_event] = EventActiveSubscriptions.new(false, event_handler) + @active_subscriptions[sdk_event] = Engine::Models::EventActiveSubscriptions.new(false, event_handler) end end @@ -52,7 +50,7 @@ def notify_internal_event(sdk_internal_event, event_metadata) # if client is not subscribed to SDK_READY if check_if_register_needed(sorted_event) @config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled - @active_subscriptions[SdkEvent::SDK_READY] = EventActiveSubscriptions.new(true, nil) + @active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil) end end end @@ -68,7 +66,7 @@ def destroy private def check_if_register_needed(sorted_event) - sorted_event == SdkEvent::SDK_READY && + sorted_event == Engine::Models::SdkEvent::SDK_READY && get_event_handler(sorted_event).nil? && !@active_subscriptions.include?(sorted_event) end @@ -112,7 +110,7 @@ def get_event_handler(sdk_event) end def get_sdk_event_if_applicable(sdk_internal_event) - final_sdk_event = ValidSdkEvent.new(nil, false) + final_sdk_event = Engine::Models::ValidSdkEvent.new(nil, false) events_to_fire = [] require_any_sdk_event = check_require_any(sdk_internal_event) @@ -120,7 +118,7 @@ def get_sdk_event_if_applicable(sdk_internal_event) if (!event_already_triggered(require_any_sdk_event.sdk_event) && execution_limit(require_any_sdk_event.sdk_event) == 1) || execution_limit(require_any_sdk_event.sdk_event) == -1 - final_sdk_event = ValidSdkEvent.new( + final_sdk_event = Engine::Models::ValidSdkEvent.new( require_any_sdk_event.sdk_event, check_prerequisites(require_any_sdk_event.sdk_event) && check_suppressed_by(require_any_sdk_event.sdk_event) @@ -180,10 +178,10 @@ def execution_limit(sdk_event) end def check_require_any(sdk_internal_event) - valid_sdk_event = ValidSdkEvent.new(nil, false) + valid_sdk_event = Engine::Models::ValidSdkEvent.new(nil, false) @manager_config.require_any.each do |name, val| if val.include?(sdk_internal_event) - valid_sdk_event = ValidSdkEvent.new(name, true) + valid_sdk_event = Engine::Models::ValidSdkEvent.new(name, true) return valid_sdk_event end end From 5fd8e94720eebebde03f6c4e81804f4becdd8ddb Mon Sep 17 00:00:00 2001 From: Bilal Al-Shahwany Date: Thu, 12 Mar 2026 10:03:48 -0700 Subject: [PATCH 46/46] updated license, changes and version --- CHANGES.txt | 5 +++++ LICENSE | 2 +- lib/splitclient-rb/version.rb | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.txt b/CHANGES.txt index ecfe21a6..e12623f5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,10 @@ CHANGES +8.11.0 (Mar, 12, 2026) +- Added the ability to listen to different events triggered by the SDK. Read more in our docs. + - SDK_UPDATE notify when a flag or user segment has changed + - SDK_READY notify when the SDK is ready to evaluate + 8.10.1 (Jan 28, 2025) - Fixed rule-based segment matcher to exit when a conition is met. - Fixed impressions properties format in redis mode. diff --git a/LICENSE b/LICENSE index 0f9e8a59..0f4dec61 100644 --- a/LICENSE +++ b/LICENSE @@ -157,7 +157,7 @@ Apache License file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2025 Harness Corporation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index ba58bfec..5b66f95f 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '8.11.0-rc2' + VERSION = '8.11.0' end