Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/splitclient-rb.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@
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/events/events_delivery.rb'
require 'splitclient-rb/engine/parser/condition'
require 'splitclient-rb/engine/parser/partition'
require 'splitclient-rb/engine/parser/evaluator'
Expand Down Expand Up @@ -119,6 +121,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'
Expand Down
20 changes: 20 additions & 0 deletions lib/splitclient-rb/engine/events/events_delivery.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 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)
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
187 changes: 187 additions & 0 deletions lib/splitclient-rb/engine/events/events_manager.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
# frozen_string_literal: true

module SplitIoClient
module Engine
module Events
class EventsManager
def initialize(events_manager_config, events_delivery, config)
@manager_config = events_manager_config
@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 == 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

@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, nil)
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(nil, 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) &&
require_value.length.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 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(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)
return valid_sdk_event
end
end

valid_sdk_event
end
end
end
end
end
6 changes: 3 additions & 3 deletions lib/splitclient-rb/engine/events/events_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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
Expand Down
14 changes: 14 additions & 0 deletions lib/splitclient-rb/engine/models/event_active_subscriptions.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# frozen_string_literal: false

module SplitIoClient
module Engine::Models
class EventActiveSubscriptions
attr_accessor :triggered, :handler

def initialize(triggered, handler)
@triggered = triggered
@handler = handler
end
end
end
end
14 changes: 14 additions & 0 deletions lib/splitclient-rb/engine/models/valid_sdk_event.rb
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion lib/splitclient-rb/engine/status_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
74 changes: 74 additions & 0 deletions spec/engine/events/events_delivery_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# frozen_string_literal: true

require 'spec_helper'

describe SplitIoClient::Engine::Events::EventsDelivery do
subject { SplitIoClient::Engine::Events::EventsDelivery }

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
Loading