Skip to content

Commit 96d2fbd

Browse files
authored
Merge pull request #617 from splitio/FME-13964-fix-firing-events-twice
- fixed multiple events fire with segment update
2 parents 259e042 + 5fd8e94 commit 96d2fbd

File tree

16 files changed

+91
-69
lines changed

16 files changed

+91
-69
lines changed

CHANGES.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
CHANGES
22

3+
8.11.0 (Mar, 12, 2026)
4+
- Added the ability to listen to different events triggered by the SDK. Read more in our docs.
5+
- SDK_UPDATE notify when a flag or user segment has changed
6+
- SDK_READY notify when the SDK is ready to evaluate
7+
38
8.10.1 (Jan 28, 2025)
49
- Fixed rule-based segment matcher to exit when a conition is met.
510
- Fixed impressions properties format in redis mode.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ Apache License
157157
file or class name and description of purpose be included on the
158158
same "printed page" as the copyright notice for easier
159159
identification within third-party archives.
160-
Copyright [yyyy] [name of copyright owner]
160+
Copyright 2025 Harness Corporation
161161
Licensed under the Apache License, Version 2.0 (the "License");
162162
you may not use this file except in compliance with the License.
163163
You may obtain a copy of the License at

lib/splitclient-rb/cache/repositories/segments_repository.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,19 @@ def add_to_segment(segment)
2323
name = segment[:name]
2424

2525
@adapter.initialize_set(segment_data(name)) unless @adapter.exists?(segment_data(name))
26-
2726
add_keys(name, segment[:added])
2827
remove_keys(name, segment[:removed])
29-
@internal_events_queue.push(
30-
SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
31-
SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED,
32-
SplitIoClient::Engine::Models::EventsMetadata.new(
33-
SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE,
34-
[]
28+
if segment[:added].length > 0 || segment[:removed].length > 0
29+
@internal_events_queue.push(
30+
SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
31+
SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED,
32+
SplitIoClient::Engine::Models::EventsMetadata.new(
33+
SplitIoClient::Engine::Models::SdkEventType::SEGMENTS_UPDATE,
34+
[]
35+
)
3536
)
3637
)
37-
)
38+
end
3839
end
3940

4041
def get_segment_keys(name)

lib/splitclient-rb/clients/split_client.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,11 @@ def destroy
118118
@config.logger.info('Split client shutdown started...') if @config.debug_enabled
119119
if !@config.cache_adapter.is_a?(SplitIoClient::Cache::Adapters::RedisAdapter) && @config.impressions_mode != :none &&
120120
(!@impressions_repository.empty? || !@events_repository.empty?)
121-
@config.logger.debug("Impressions and/or Events cache is not empty")
121+
@config.logger.debug("Impressions and/or Events cache is not empty") if @config.debug_enabled
122122
# Adding small delay to ensure sender threads are fully running
123123
sleep(0.1)
124124
if !@config.threads.key?(:impressions_sender) || !@config.threads.key?(:events_sender)
125-
@config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.")
125+
@config.logger.debug("Periodic data recording thread has not started yet, waiting for service startup.") if @config.debug_enabled
126126
@config.threads[:start_sdk].join(5) if @config.threads.key?(:start_sdk)
127127
end
128128
end

lib/splitclient-rb/engine/api/splits.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def since(since, since_rbs, fetch_options = { cache_control_headers: false, till
2424

2525
if check_last_proxy_check_timestamp
2626
@spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
27-
@config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.")
27+
@config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.") if @config.debug_enabled
2828
@old_spec_since = since
2929
since = -1
3030
since_rbs = -1
@@ -41,7 +41,7 @@ def since(since, since_rbs, fetch_options = { cache_control_headers: false, till
4141

4242
params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
4343
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
44-
@config.logger.debug("Fetching from splitChanges with #{params}: ")
44+
@config.logger.debug("Fetching from splitChanges with #{params}: ") if @config.debug_enabled
4545
response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
4646
if response.status == 414
4747
@config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.")

lib/splitclient-rb/engine/auth_api_client.rb

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ def authenticate(api_key)
2121
return process_error(response) if response.status >= 400 && response.status < 500
2222

2323
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::TOKEN_SYNC, response.status.to_i)
24-
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
24+
if @config.debug_enabled
25+
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
26+
end
2527
{ push_enabled: false, retry: true }
2628
rescue StandardError => e
27-
@config.logger.debug("AuthApiClient error: #{e.inspect}.")
29+
@config.logger.debug("AuthApiClient error: #{e.inspect}.") if @config.debug_enabled
2830
{ push_enabled: false, retry: false }
2931
end
3032

@@ -51,7 +53,9 @@ def decode_token(token)
5153
end
5254

5355
def process_error(response)
54-
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
56+
if @config.debug_enabled
57+
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
58+
end
5559
@telemetry_runtime_producer.record_auth_rejections if response.status == 401
5660

5761
{ push_enabled: false, retry: false }

lib/splitclient-rb/engine/events/events_manager.rb

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ def register(sdk_event, event_handler)
1818

1919
@mutex.synchronize do
2020
# SDK ready already fired
21-
if sdk_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event)
22-
@active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(true, event_handler)
21+
if sdk_event == Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event)
22+
@active_subscriptions[sdk_event] = Engine::Models::EventActiveSubscriptions.new(true, event_handler)
2323
@config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled
2424
fire_sdk_event(sdk_event, nil)
2525
return
2626
end
2727

2828
@config.logger.debug("EventsManager: Register event: #{sdk_event}") if @config.debug_enabled
29-
@active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(false, event_handler)
29+
@active_subscriptions[sdk_event] = Engine::Models::EventActiveSubscriptions.new(false, event_handler)
3030
end
3131
end
3232

@@ -48,7 +48,7 @@ def notify_internal_event(sdk_internal_event, event_metadata)
4848
end
4949

5050
# if client is not subscribed to SDK_READY
51-
if sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && get_event_handler(sorted_event).nil?
51+
if check_if_register_needed(sorted_event)
5252
@config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled
5353
@active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil)
5454
end
@@ -65,6 +65,12 @@ def destroy
6565

6666
private
6767

68+
def check_if_register_needed(sorted_event)
69+
sorted_event == Engine::Models::SdkEvent::SDK_READY &&
70+
get_event_handler(sorted_event).nil? &&
71+
!@active_subscriptions.include?(sorted_event)
72+
end
73+
6874
def fire_sdk_event(sdk_event, event_metadata)
6975
@config.logger.debug("EventsManager: Firing Sdk event: #{sdk_event}") if @config.debug_enabled
7076
@config.threads[:sdk_event_notify] = Thread.new do
@@ -104,15 +110,15 @@ def get_event_handler(sdk_event)
104110
end
105111

106112
def get_sdk_event_if_applicable(sdk_internal_event)
107-
final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false)
113+
final_sdk_event = Engine::Models::ValidSdkEvent.new(nil, false)
108114

109115
events_to_fire = []
110116
require_any_sdk_event = check_require_any(sdk_internal_event)
111117
if require_any_sdk_event.valid
112118
if (!event_already_triggered(require_any_sdk_event.sdk_event) &&
113119
execution_limit(require_any_sdk_event.sdk_event) == 1) ||
114120
execution_limit(require_any_sdk_event.sdk_event) == -1
115-
final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(
121+
final_sdk_event = Engine::Models::ValidSdkEvent.new(
116122
require_any_sdk_event.sdk_event,
117123
check_prerequisites(require_any_sdk_event.sdk_event) &&
118124
check_suppressed_by(require_any_sdk_event.sdk_event)
@@ -172,10 +178,10 @@ def execution_limit(sdk_event)
172178
end
173179

174180
def check_require_any(sdk_internal_event)
175-
valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false)
181+
valid_sdk_event = Engine::Models::ValidSdkEvent.new(nil, false)
176182
@manager_config.require_any.each do |name, val|
177183
if val.include?(sdk_internal_event)
178-
valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true)
184+
valid_sdk_event = Engine::Models::ValidSdkEvent.new(name, true)
179185
return valid_sdk_event
180186
end
181187
end

lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
# frozen_string_literal: false
22

33
module SplitIoClient
4-
module Engine::Models
5-
class SdkInternalEventNotification
6-
attr_reader :internal_event, :metadata
4+
module Engine
5+
module Models
6+
class SdkInternalEventNotification
7+
attr_reader :internal_event, :metadata
78

8-
def initialize(internal_event, metadata)
9-
@internal_event = internal_event
10-
@metadata = metadata
9+
def initialize(internal_event, metadata)
10+
@internal_event = internal_event
11+
@metadata = metadata
12+
end
1113
end
1214
end
1315
end

lib/splitclient-rb/engine/sync_manager.rb

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,13 @@ def start_thread
4747
connected = false
4848

4949
if @config.streaming_enabled
50-
@config.logger.debug('Starting Streaming mode ...')
50+
@config.logger.debug('Starting Streaming mode ...') if @config.debug_enabled
5151
start_push_status_monitor
5252
connected = @push_manager.start_sse
5353
end
5454

5555
unless connected
56-
@config.logger.debug('Starting Polling mode ...')
56+
@config.logger.debug('Starting Polling mode ...') if @config.debug_enabled
5757
@synchronizer.start_periodic_fetch
5858
record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING)
5959
end
@@ -92,7 +92,7 @@ def process_push_shutdown
9292

9393
def process_connected
9494
if @sse_connected.value
95-
@config.logger.debug('Streaming already connected.')
95+
@config.logger.debug('Streaming already connected.') if @config.debug_enabled
9696
return
9797
end
9898

@@ -107,7 +107,7 @@ def process_connected
107107

108108
def process_forced_stop
109109
unless @sse_connected.value
110-
@config.logger.debug('Streaming already disconnected.')
110+
@config.logger.debug('Streaming already disconnected.') if @config.debug_enabled
111111
return
112112
end
113113

@@ -120,7 +120,7 @@ def process_forced_stop
120120

121121
def process_disconnect(reconnect)
122122
unless @sse_connected.value
123-
@config.logger.debug('Streaming already disconnected.')
123+
@config.logger.debug('Streaming already disconnected.') if @config.debug_enabled
124124
return
125125
end
126126

@@ -169,12 +169,16 @@ def incoming_push_status_handler
169169
when Constants::PUSH_SUBSYSTEM_OFF
170170
process_push_shutdown
171171
else
172-
@config.logger.debug('Incorrect push status type.')
172+
log_if_debug('Incorrect push status type.')
173173
end
174174
end
175175
rescue StandardError => e
176176
@config.logger.error("Push status handler error: #{e.inspect}")
177177
end
178178
end
179+
180+
def log_if_debug(msg)
181+
@config.logger.debug(msg) if @config.debug_enabled
182+
end
179183
end
180184
end

lib/splitclient-rb/sse/event_source/client.rb

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -38,23 +38,23 @@ def initialize(config,
3838

3939
def close(status = nil)
4040
unless connected?
41-
@config.logger.debug('SSEClient already disconected.')
41+
@config.logger.debug('SSEClient already disconected.') if @config.debug_enabled
4242
return
4343
end
44-
@config.logger.debug("Closing SSEClient socket")
44+
@config.logger.debug("Closing SSEClient socket") if @config.debug_enabled
4545

4646
push_status(status)
4747
@connected.make_false
4848
@socket.sync_close = true if @socket.is_a? OpenSSL::SSL::SSLSocket
4949
@socket.close
50-
@config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket
50+
@config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket && @config.debug_enabled
5151
rescue StandardError => e
5252
@config.logger.error("SSEClient close Error: #{e.inspect}")
5353
end
5454

5555
def start(url)
5656
if connected?
57-
@config.logger.debug('SSEClient already running.')
57+
@config.logger.debug('SSEClient already running.') if @config.debug_enabled
5858
return true
5959
end
6060

@@ -96,18 +96,17 @@ def connect_stream(latch)
9696

9797
raise 'eof exception' if partial_data == :eof
9898
rescue IO::WaitReadable => e
99-
@config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}")
99+
@config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") if @config.debug_enabled
100100
IO.select([@socket], nil, nil, @read_timeout)
101101
retry
102102
rescue Errno::EAGAIN => e
103-
@config.logger.debug("SSE client transient error: #{e.inspect}")
103+
@config.logger.debug("SSE client transient error: #{e.inspect}") if @config.debug_enabled
104104
IO.select([@socket], nil, nil, @read_timeout)
105105
retry
106106
rescue Errno::ETIMEDOUT => e
107107
@config.logger.error("SSE read operation timed out!: #{e.inspect}")
108108
return Constants::PUSH_RETRYABLE_ERROR
109109
rescue EOFError => e
110-
puts "SSE read operation EOF Exception!: #{e.inspect}"
111110
@config.logger.error("SSE read operation EOF Exception!: #{e.inspect}")
112111
raise 'eof exception'
113112
rescue Errno::EBADF, IOError => e
@@ -125,12 +124,12 @@ def connect_stream(latch)
125124
return Constants::PUSH_RETRYABLE_ERROR
126125
end
127126
rescue Errno::EBADF
128-
@config.logger.debug("SSE socket is not connected (Errno::EBADF)")
127+
@config.logger.debug("SSE socket is not connected (Errno::EBADF)") if @config.debug_enabled
129128
break
130129
rescue RuntimeError
131130
raise 'eof exception'
132131
rescue Exception => e
133-
@config.logger.debug("SSE socket is not connected: #{e.inspect}")
132+
@config.logger.debug("SSE socket is not connected: #{e.inspect}") if @config.debug_enabled
134133
break
135134
end
136135

@@ -156,7 +155,7 @@ def read_first_event(data, latch)
156155
return unless @first_event.value
157156

158157
response_code = @event_parser.first_event(data)
159-
@config.logger.debug("SSE client first event code: #{response_code}")
158+
@config.logger.debug("SSE client first event code: #{response_code}") if @config.debug_enabled
160159

161160
error_event = false
162161
events = @event_parser.parse(data)
@@ -165,7 +164,7 @@ def read_first_event(data, latch)
165164

166165
if response_code == OK_CODE && !error_event
167166
@connected.make_true
168-
@config.logger.debug("SSE client first event Connected is true")
167+
@config.logger.debug("SSE client first event Connected is true") if @config.debug_enabled
169168
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::SSE_CONNECTION_ESTABLISHED, nil)
170169
push_status(Constants::PUSH_CONNECTED)
171170
end
@@ -202,7 +201,7 @@ def socket_connect
202201
end
203202

204203
def process_data(partial_data)
205-
@config.logger.debug("Event partial data: #{partial_data}")
204+
@config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
206205
return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE
207206

208207
events = @event_parser.parse(partial_data)
@@ -220,7 +219,7 @@ def build_request(uri)
220219
req << "SplitSDKMachineName: #{@config.machine_name}\r\n"
221220
req << "SplitSDKClientKey: #{@api_key.split(//).last(4).join}\r\n" unless @api_key.nil?
222221
req << "Cache-Control: no-cache\r\n\r\n"
223-
@config.logger.debug("Request info: #{req}")
222+
@config.logger.debug("Request info: #{req}") if @config.debug_enabled
224223
req
225224
end
226225

@@ -255,7 +254,7 @@ def dispatch_event(event)
255254
def push_status(status)
256255
return if status.nil?
257256

258-
@config.logger.debug("Pushing new sse status: #{status}")
257+
@config.logger.debug("Pushing new sse status: #{status}") if @config.debug_enabled
259258
@status_queue.push(status)
260259
end
261260
end

0 commit comments

Comments
 (0)