Skip to content

Commit bea83f5

Browse files
authored
Merge pull request #615 from splitio/FME-13560-events-manager
Fme 13560 events manager and delivery
2 parents 553dae2 + 79ab3d0 commit bea83f5

9 files changed

Lines changed: 477 additions & 4 deletions

File tree

lib/splitclient-rb.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@
6767
require 'splitclient-rb/engine/common/impressions_manager'
6868
require 'splitclient-rb/engine/common/noop_impressions_counter'
6969
require 'splitclient-rb/engine/events/events_manager_config.rb'
70+
require 'splitclient-rb/engine/events/events_manager.rb'
7071
require 'splitclient-rb/engine/events/events_task.rb'
72+
require 'splitclient-rb/engine/events/events_delivery.rb'
7173
require 'splitclient-rb/engine/parser/condition'
7274
require 'splitclient-rb/engine/parser/partition'
7375
require 'splitclient-rb/engine/parser/evaluator'
@@ -119,6 +121,8 @@
119121
require 'splitclient-rb/engine/models/sdk_event.rb'
120122
require 'splitclient-rb/engine/models/sdk_internal_event.rb'
121123
require 'splitclient-rb/engine/models/sdk_internal_event_notification.rb'
124+
require 'splitclient-rb/engine/models/valid_sdk_event.rb'
125+
require 'splitclient-rb/engine/models/event_active_subscriptions.rb'
122126
require 'splitclient-rb/engine/auth_api_client'
123127
require 'splitclient-rb/engine/back_off'
124128
require 'splitclient-rb/engine/fallback_treatment_calculator.rb'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# frozen_string_literal: true
2+
3+
module SplitIoClient
4+
module Engine
5+
module Events
6+
class EventsDelivery
7+
def initialize(config)
8+
@config = config
9+
end
10+
11+
def deliver(sdk_event, event_metadata, event_handler)
12+
event_handler.call(event_metadata)
13+
rescue StandardError => e
14+
@config.logger.error("Exception when calling handler for Sdk Event #{sdk_event}")
15+
@config.log_found_exception(__method__.to_s, e)
16+
end
17+
end
18+
end
19+
end
20+
end
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
# frozen_string_literal: true
2+
3+
module SplitIoClient
4+
module Engine
5+
module Events
6+
class EventsManager
7+
def initialize(events_manager_config, events_delivery, config)
8+
@manager_config = events_manager_config
9+
@events_delivery = events_delivery
10+
@active_subscriptions = {}
11+
@internal_events_status = {}
12+
@mutex = Mutex.new
13+
@config = config
14+
end
15+
16+
def register(sdk_event, event_handler)
17+
return unless !@active_subscriptions.key?(sdk_event) || get_event_handler(sdk_event).nil?
18+
19+
@mutex.synchronize do
20+
# 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)
23+
@config.logger.debug('EventsManager: Firing SDK_READY event for new subscription') if @config.debug_enabled
24+
fire_sdk_event(sdk_event, nil)
25+
return
26+
end
27+
28+
@active_subscriptions[sdk_event] = SplitIoClient::Engine::Models::EventActiveSubscriptions.new(false, event_handler)
29+
end
30+
end
31+
32+
def unregister(sdk_event)
33+
return unless @active_subscriptions.key?(sdk_event)
34+
35+
@mutex.synchronize do
36+
@active_subscriptions.delete(sdk_event)
37+
end
38+
end
39+
40+
def notify_internal_event(sdk_internal_event, event_metadata)
41+
@mutex.synchronize do
42+
update_internal_event_status(sdk_internal_event, true)
43+
@manager_config.evaluation_order.each do |sorted_event|
44+
if get_sdk_event_if_applicable(sdk_internal_event).include?(sorted_event) &&
45+
!get_event_handler(sorted_event).nil?
46+
fire_sdk_event(sorted_event, event_metadata)
47+
end
48+
49+
# if client is not subscribed to SDK_READY
50+
if sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY && get_event_handler(sorted_event).nil?
51+
@config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled
52+
@active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil)
53+
end
54+
end
55+
end
56+
end
57+
58+
def destroy
59+
@mutex.synchronize do
60+
@active_subscriptions = {}
61+
@internal_events_status = {}
62+
end
63+
end
64+
65+
private
66+
67+
def fire_sdk_event(sdk_event, event_metadata)
68+
@config.logger.debug("EventsManager: Firing Sdk event: #{sdk_event}") if @config.debug_enabled
69+
@config.threads[:sdk_event_notify] = Thread.new do
70+
@events_delivery.deliver(sdk_event, event_metadata, get_event_handler(sdk_event))
71+
end
72+
sdk_event_triggered(sdk_event)
73+
end
74+
75+
def event_already_triggered(sdk_event)
76+
return @active_subscriptions[sdk_event].triggered if @active_subscriptions.key?(sdk_event)
77+
78+
false
79+
end
80+
81+
def get_internal_event_status(sdk_internal_event)
82+
return @internal_events_status[sdk_internal_event] if @internal_events_status.key?(sdk_internal_event)
83+
84+
false
85+
end
86+
87+
def update_internal_event_status(sdk_internal_event, status)
88+
@internal_events_status[sdk_internal_event] = status
89+
end
90+
91+
def sdk_event_triggered(sdk_event)
92+
return unless @active_subscriptions.key?(sdk_event)
93+
94+
return if @active_subscriptions[sdk_event].triggered
95+
96+
@active_subscriptions[sdk_event].triggered = true
97+
end
98+
99+
def get_event_handler(sdk_event)
100+
return nil unless @active_subscriptions.key?(sdk_event)
101+
102+
@active_subscriptions[sdk_event].handler
103+
end
104+
105+
def get_sdk_event_if_applicable(sdk_internal_event)
106+
final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false)
107+
108+
events_to_fire = []
109+
require_any_sdk_event = check_require_any(sdk_internal_event)
110+
if require_any_sdk_event.valid
111+
if (!event_already_triggered(require_any_sdk_event.sdk_event) &&
112+
execution_limit(require_any_sdk_event.sdk_event) == 1) ||
113+
execution_limit(require_any_sdk_event.sdk_event) == -1
114+
final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(
115+
require_any_sdk_event.sdk_event,
116+
check_prerequisites(require_any_sdk_event.sdk_event) &&
117+
check_suppressed_by(require_any_sdk_event.sdk_event)
118+
)
119+
end
120+
events_to_fire.push(final_sdk_event.sdk_event) if final_sdk_event.valid
121+
end
122+
check_require_all.each { |sdk_event| events_to_fire.push(sdk_event) }
123+
124+
events_to_fire
125+
end
126+
127+
def check_require_all
128+
events = []
129+
@manager_config.require_all.each do |require_name, require_value|
130+
final_status = true
131+
require_value.each { |val| final_status &= get_internal_event_status(val) }
132+
events.push(require_name) if check_event_eligible_conditions(final_status, require_name, require_value)
133+
end
134+
135+
events
136+
end
137+
138+
def check_event_eligible_conditions(final_status, require_name, require_value)
139+
final_status &&
140+
check_prerequisites(require_name) &&
141+
((!event_already_triggered(require_name) &&
142+
execution_limit(require_name) == 1) ||
143+
execution_limit(require_name) == -1) &&
144+
require_value.length.positive?
145+
end
146+
147+
def check_prerequisites(sdk_event)
148+
@manager_config.prerequisites.each do |name, value|
149+
value.each do |val|
150+
return false if name == sdk_event && !event_already_triggered(val)
151+
end
152+
end
153+
154+
true
155+
end
156+
157+
def check_suppressed_by(sdk_event)
158+
@manager_config.suppressed_by.each do |name, value|
159+
value.each do |val|
160+
return false if name == sdk_event && event_already_triggered(val)
161+
end
162+
end
163+
164+
true
165+
end
166+
167+
def execution_limit(sdk_event)
168+
return -1 unless @manager_config.execution_limits.key?(sdk_event)
169+
170+
@manager_config.execution_limits[sdk_event]
171+
end
172+
173+
def check_require_any(sdk_internal_event)
174+
valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false)
175+
@manager_config.require_any.each do |name, val|
176+
if val.include?(sdk_internal_event)
177+
valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true)
178+
return valid_sdk_event
179+
end
180+
end
181+
182+
valid_sdk_event
183+
end
184+
end
185+
end
186+
end
187+
end

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def initialize(notify_internal_events, internal_events_queue, config)
1616
def start
1717
return if @running
1818

19-
@config.logger.info('Starting Internal Events Task.') if @config.debug_enabled
19+
@config.logger.info('Starting Internal Events Task.')
2020
@running = true
2121
@config.threads[:internal_events_task] = Thread.new do
2222
worker_thread
@@ -26,7 +26,7 @@ def start
2626
def stop
2727
return unless @running
2828

29-
@config.logger.info('Stopping Internal Events Task.') if @config.debug_enabled
29+
@config.logger.info('Stopping Internal Events Task.')
3030
@running = false
3131
end
3232

@@ -36,7 +36,7 @@ def worker_thread
3636
while (event = @internal_events_queue.pop)
3737
break unless @running
3838

39-
@config.logger.info("Processing sdk internal event: #{event.internal_event}") if @config.debug_enabled
39+
@config.logger.debug("Processing sdk internal event: #{event.internal_event}") if @config.debug_enabled
4040
begin
4141
@notify_internal_events.call(event.internal_event, event.metadata)
4242
rescue StandardError => e
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: false
2+
3+
module SplitIoClient
4+
module Engine::Models
5+
class EventActiveSubscriptions
6+
attr_accessor :triggered, :handler
7+
8+
def initialize(triggered, handler)
9+
@triggered = triggered
10+
@handler = handler
11+
end
12+
end
13+
end
14+
end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# frozen_string_literal: false
2+
3+
module SplitIoClient
4+
module Engine::Models
5+
class ValidSdkEvent
6+
attr_reader :sdk_event, :valid
7+
8+
def initialize(sdk_event, valid)
9+
@sdk_event = sdk_event
10+
@valid = valid
11+
end
12+
end
13+
end
14+
end

lib/splitclient-rb/engine/status_manager.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ def ready!
2222
@config.logger.info('SplitIO SDK is ready')
2323
@internal_events_queue.push(
2424
SplitIoClient::Engine::Models::SdkInternalEventNotification.new(
25-
SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil)
25+
SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY, nil
26+
)
2627
)
2728
end
2829

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
describe SplitIoClient::Engine::Events::EventsDelivery do
6+
subject { SplitIoClient::Engine::Events::EventsDelivery }
7+
8+
it 'calls handler successfully' do
9+
config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new))
10+
delivery = subject.new(config)
11+
12+
delivery.deliver(
13+
SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
14+
SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE),
15+
method(:call_back)
16+
)
17+
sleep 0.5
18+
expect(@metadata.type).to be(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE)
19+
end
20+
21+
it 'handles exception when calling handler' do
22+
log = StringIO.new
23+
config = SplitIoClient::SplitConfig.new(logger: Logger.new(log))
24+
delivery = subject.new(config)
25+
26+
delivery.deliver(
27+
SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
28+
SplitIoClient::Engine::Models::EventsMetadata.new(SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE),
29+
method(:call_with_exception)
30+
)
31+
sleep 0.5
32+
expect(log.string).to include('Exception when calling handler for Sdk Event')
33+
end
34+
35+
it 'logs the sdk event name when handler raises exception' do
36+
log = StringIO.new
37+
config = SplitIoClient::SplitConfig.new(logger: Logger.new(log))
38+
delivery = subject.new(config)
39+
40+
delivery.deliver(
41+
SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY,
42+
nil,
43+
method(:call_with_exception)
44+
)
45+
sleep 0.5
46+
expect(log.string).to include('Exception when calling handler for Sdk Event')
47+
expect(log.string).to include(SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY.to_s)
48+
end
49+
50+
it 'calls handler with correct metadata' do
51+
config = SplitIoClient::SplitConfig.new(logger: Logger.new(StringIO.new))
52+
delivery = subject.new(config)
53+
metadata = SplitIoClient::Engine::Models::EventsMetadata.new(
54+
SplitIoClient::Engine::Models::SdkEventType::FLAG_UPDATE,
55+
['feature1', 'feature2']
56+
)
57+
58+
delivery.deliver(
59+
SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
60+
metadata,
61+
method(:call_back)
62+
)
63+
sleep 0.5
64+
expect(@metadata).to eq(metadata)
65+
end
66+
67+
def call_back(metadata)
68+
@metadata = metadata
69+
end
70+
71+
def call_with_exception(_metadata)
72+
raise StandardError, 'call exception'
73+
end
74+
end

0 commit comments

Comments
 (0)