splitclient-rb 8.4.0 → 8.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -2
- data/CHANGES.txt +24 -0
- data/LICENSE +169 -13
- data/NOTICE.txt +5 -0
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +9 -7
- data/lib/splitclient-rb/cache/filter/bloom_filter.rb +10 -6
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +1 -0
- data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +21 -2
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +42 -12
- data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +24 -10
- data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +2 -1
- data/lib/splitclient-rb/clients/split_client.rb +146 -62
- data/lib/splitclient-rb/engine/api/client.rb +8 -0
- data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
- data/lib/splitclient-rb/engine/api/splits.rb +100 -24
- data/lib/splitclient-rb/engine/auth_api_client.rb +3 -3
- data/lib/splitclient-rb/engine/common/impressions_manager.rb +56 -30
- data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
- data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
- data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
- data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
- data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
- data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
- data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +58 -11
- data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +3 -1
- data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
- data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
- data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
- data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
- data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
- data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
- data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
- data/lib/splitclient-rb/engine/models/label.rb +1 -0
- data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +14 -0
- data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
- data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
- data/lib/splitclient-rb/engine/parser/condition.rb +9 -0
- data/lib/splitclient-rb/engine/parser/evaluator.rb +24 -30
- data/lib/splitclient-rb/engine/status_manager.rb +7 -1
- data/lib/splitclient-rb/engine/sync_manager.rb +6 -6
- data/lib/splitclient-rb/engine/synchronizer.rb +37 -17
- data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
- data/lib/splitclient-rb/helpers/repository_helper.rb +39 -1
- data/lib/splitclient-rb/helpers/util.rb +12 -3
- data/lib/splitclient-rb/managers/split_manager.rb +2 -1
- data/lib/splitclient-rb/spec.rb +1 -1
- data/lib/splitclient-rb/split_config.rb +45 -5
- data/lib/splitclient-rb/split_factory.rb +27 -21
- data/lib/splitclient-rb/sse/event_source/client.rb +86 -29
- data/lib/splitclient-rb/sse/event_source/event_parser.rb +2 -2
- data/lib/splitclient-rb/sse/event_source/event_types.rb +1 -0
- data/lib/splitclient-rb/sse/notification_manager_keeper.rb +3 -3
- data/lib/splitclient-rb/sse/notification_processor.rb +3 -1
- data/lib/splitclient-rb/sse/workers/segments_worker.rb +4 -4
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +63 -24
- data/lib/splitclient-rb/utilitites.rb +6 -10
- data/lib/splitclient-rb/validators.rb +56 -25
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +22 -0
- data/sonar-project.properties +1 -0
- data/splitclient-rb.gemspec +3 -4
- metadata +32 -38
|
@@ -5,58 +5,134 @@ module SplitIoClient
|
|
|
5
5
|
# Retrieves split definitions from the Split Backend
|
|
6
6
|
class Splits < Client
|
|
7
7
|
|
|
8
|
+
PROXY_CHECK_INTERVAL_SECONDS = 24 * 60 * 60
|
|
9
|
+
SPEC_1_1 = "1.1"
|
|
10
|
+
|
|
8
11
|
def initialize(api_key, config, telemetry_runtime_producer)
|
|
9
12
|
super(config)
|
|
10
13
|
@api_key = api_key
|
|
11
14
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
12
15
|
@flag_sets_filter = @config.flag_sets_filter
|
|
16
|
+
@spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
|
17
|
+
@last_proxy_check_timestamp = 0
|
|
18
|
+
@clear_storage = false
|
|
19
|
+
@old_spec_since = nil
|
|
13
20
|
end
|
|
14
21
|
|
|
15
|
-
def since(since, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
|
|
22
|
+
def since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil, sets: nil})
|
|
16
23
|
start = Time.now
|
|
24
|
+
|
|
25
|
+
if check_last_proxy_check_timestamp
|
|
26
|
+
@spec_version = SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
|
27
|
+
@config.logger.debug("Switching to new Feature flag spec #{@spec_version} and fetching.") if @config.debug_enabled
|
|
28
|
+
@old_spec_since = since
|
|
29
|
+
since = -1
|
|
30
|
+
since_rbs = -1
|
|
31
|
+
fetch_options = { cache_control_headers: false, till: nil, sets: nil}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
if @spec_version == Splits::SPEC_1_1
|
|
35
|
+
since = @old_spec_since unless @old_spec_since.nil?
|
|
36
|
+
params = { s: @spec_version, since: since }
|
|
37
|
+
@old_spec_since = nil
|
|
38
|
+
else
|
|
39
|
+
params = { s: @spec_version, since: since, rbSince: since_rbs }
|
|
40
|
+
end
|
|
17
41
|
|
|
18
|
-
params = { s: SplitIoClient::Spec::FeatureFlags::SPEC_VERSION, since: since }
|
|
19
42
|
params[:sets] = @flag_sets_filter.join(",") unless @flag_sets_filter.empty?
|
|
20
43
|
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
|
|
21
|
-
@config.logger.debug("Fetching from splitChanges with #{params}: ")
|
|
44
|
+
@config.logger.debug("Fetching from splitChanges with #{params}: ") if @config.debug_enabled
|
|
22
45
|
response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
|
|
23
46
|
if response.status == 414
|
|
24
47
|
@config.logger.error("Error fetching feature flags; the amount of flag sets provided are too big, causing uri length error.")
|
|
25
48
|
raise ApiException.new response.body, 414
|
|
26
49
|
end
|
|
50
|
+
|
|
51
|
+
if response.status == 400 and sdk_url_overriden? and @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
|
52
|
+
@config.logger.warn("Detected proxy response error, changing spec version from #{@spec_version} to #{Splits::SPEC_1_1} and re-fetching.")
|
|
53
|
+
@spec_version = Splits::SPEC_1_1
|
|
54
|
+
@last_proxy_check_timestamp = Time.now
|
|
55
|
+
return since(since, 0, fetch_options = {cache_control_headers: fetch_options[:cache_control_headers], till: fetch_options[:till],
|
|
56
|
+
sets: fetch_options[:sets]})
|
|
57
|
+
end
|
|
27
58
|
if response.success?
|
|
28
|
-
result =
|
|
29
|
-
unless result[:splits].empty?
|
|
30
|
-
@config.split_logger.log_if_debug("#{result[:splits].length} feature flags retrieved. since=#{since}")
|
|
31
|
-
end
|
|
32
|
-
@config.split_logger.log_if_transport("Feature flag changes response: #{result.to_s}")
|
|
59
|
+
result = JSON.parse(response.body, symbolize_names: true)
|
|
33
60
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
61
|
+
return process_result(result, since, since_rbs, start)
|
|
62
|
+
end
|
|
63
|
+
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
|
|
37
64
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@telemetry_runtime_producer.record_sync_error(Telemetry::Domain::Constants::SPLIT_SYNC, response.status)
|
|
65
|
+
@config.logger.error("Unexpected status code while fetching feature flags: #{response.status}. " \
|
|
66
|
+
'Check your API key and base URI')
|
|
41
67
|
|
|
42
|
-
|
|
43
|
-
|
|
68
|
+
raise 'Split SDK failed to connect to backend to fetch feature flags definitions'
|
|
69
|
+
end
|
|
44
70
|
|
|
45
|
-
|
|
46
|
-
|
|
71
|
+
def clear_storage
|
|
72
|
+
@clear_storage
|
|
47
73
|
end
|
|
48
74
|
|
|
49
75
|
private
|
|
50
76
|
|
|
51
|
-
def
|
|
52
|
-
|
|
77
|
+
def process_result(result, since, since_rbs, start)
|
|
78
|
+
if @spec_version == Splits::SPEC_1_1
|
|
79
|
+
result = convert_to_new_spec(result)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
result[:rbs][:d] = check_rbs_data(result[:rbs][:d])
|
|
83
|
+
result = objects_with_segment_names(result)
|
|
84
|
+
|
|
85
|
+
if @spec_version == SplitIoClient::Spec::FeatureFlags::SPEC_VERSION
|
|
86
|
+
@clear_storage = @last_proxy_check_timestamp != 0
|
|
87
|
+
@last_proxy_check_timestamp = 0
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
unless result[:ff][:d].empty?
|
|
91
|
+
@config.split_logger.log_if_debug("#{result[:ff][:d].length} feature flags retrieved. since=#{since}")
|
|
92
|
+
end
|
|
93
|
+
@config.split_logger.log_if_transport("Feature flag changes response: #{result[:ff].to_s}")
|
|
94
|
+
|
|
95
|
+
unless result[:rbs][:d].empty?
|
|
96
|
+
@config.split_logger.log_if_debug("#{result[:rbs][:d].length} rule based segments retrieved. since=#{since_rbs}")
|
|
97
|
+
end
|
|
98
|
+
@config.split_logger.log_if_transport("rule based segments changes response: #{result[:rbs].to_s}")
|
|
99
|
+
|
|
100
|
+
bucket = BinarySearchLatencyTracker.get_bucket((Time.now - start) * 1000.0)
|
|
101
|
+
@telemetry_runtime_producer.record_sync_latency(Telemetry::Domain::Constants::SPLIT_SYNC, bucket)
|
|
102
|
+
@telemetry_runtime_producer.record_successful_sync(Telemetry::Domain::Constants::SPLIT_SYNC, (Time.now.to_f * 1000.0).to_i)
|
|
103
|
+
|
|
104
|
+
result
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def check_rbs_data(rbs_data)
|
|
108
|
+
rbs_data.each do |rb_segment|
|
|
109
|
+
rb_segment[:excluded] = {:keys => [], :segments => []} if rb_segment[:excluded].nil?
|
|
110
|
+
rb_segment[:excluded][:keys] = [] if rb_segment[:excluded][:keys].nil?
|
|
111
|
+
rb_segment[:excluded][:segments] = [] if rb_segment[:excluded][:segments].nil?
|
|
112
|
+
end
|
|
113
|
+
rbs_data
|
|
114
|
+
end
|
|
53
115
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
116
|
+
def objects_with_segment_names(parsed_objects)
|
|
117
|
+
parsed_objects[:segment_names] = Set.new
|
|
118
|
+
parsed_objects[:segment_names] =
|
|
119
|
+
parsed_objects[:ff][:d].each_with_object(Set.new) do |split, splits|
|
|
120
|
+
splits << Helpers::Util.segment_names_by_object(split, "IN_SEGMENT")
|
|
57
121
|
end.flatten
|
|
58
122
|
|
|
59
|
-
|
|
123
|
+
parsed_objects[:rbs][:d].each do |rule_based_segment|
|
|
124
|
+
parsed_objects[:segment_names].merge Helpers::Util.segment_names_in_rb_segment(rule_based_segment, "IN_SEGMENT")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
parsed_objects
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def check_last_proxy_check_timestamp
|
|
131
|
+
@spec_version == Splits::SPEC_1_1 and ((Time.now - @last_proxy_check_timestamp) >= Splits::PROXY_CHECK_INTERVAL_SECONDS)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def convert_to_new_spec(body)
|
|
135
|
+
{:ff => {:d => body[:splits], :s => body[:since], :t => body[:till]}, :rbs => {:d => [], :s => -1, :t => -1}}
|
|
60
136
|
end
|
|
61
137
|
end
|
|
62
138
|
end
|
|
@@ -21,10 +21,10 @@ module SplitIoClient
|
|
|
21
21
|
return process_error(response) if response.status >= 400 && response.status < 500
|
|
22
22
|
|
|
23
23
|
@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
|
+
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") if @config.debug_enabled
|
|
25
25
|
{ push_enabled: false, retry: true }
|
|
26
26
|
rescue StandardError => e
|
|
27
|
-
@config.logger.debug("AuthApiClient error: #{e.inspect}.")
|
|
27
|
+
@config.logger.debug("AuthApiClient error: #{e.inspect}.") if @config.debug_enabled
|
|
28
28
|
{ push_enabled: false, retry: false }
|
|
29
29
|
end
|
|
30
30
|
|
|
@@ -51,7 +51,7 @@ module SplitIoClient
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def process_error(response)
|
|
54
|
-
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
|
|
54
|
+
@config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}") if @config.debug_enabled
|
|
55
55
|
@telemetry_runtime_producer.record_auth_rejections if response.status == 401
|
|
56
56
|
|
|
57
57
|
{ push_enabled: false, retry: false }
|
|
@@ -18,20 +18,24 @@ module SplitIoClient
|
|
|
18
18
|
@unique_keys_tracker = unique_keys_tracker
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
def build_impression(matching_key, bucketing_key, split_name,
|
|
22
|
-
|
|
21
|
+
def build_impression(matching_key, bucketing_key, split_name, treatment_data, impressions_disabled, params = {},
|
|
22
|
+
evaluation_options = nil)
|
|
23
|
+
properties = get_properties(evaluation_options)
|
|
24
|
+
impression_data = impression_data(matching_key, bucketing_key, split_name, treatment_data, params[:time], properties)
|
|
25
|
+
return impression(impression_data, params[:attributes]) if check_return_conditions(properties)
|
|
23
26
|
|
|
24
27
|
begin
|
|
25
|
-
|
|
26
|
-
when :debug # In DEBUG mode we should calculate the pt only.
|
|
27
|
-
impression_data[:pt] = @impression_observer.test_and_set(impression_data)
|
|
28
|
-
when :none # In NONE mode we should track the total amount of evaluations and the unique keys.
|
|
28
|
+
if check_none_mode(impressions_disabled)
|
|
29
29
|
@impression_counter.inc(split_name, impression_data[:m])
|
|
30
30
|
@unique_keys_tracker.track(split_name, matching_key)
|
|
31
|
-
|
|
31
|
+
end
|
|
32
|
+
if check_observe_impressions
|
|
33
|
+
# In DEBUG mode we should calculate the pt only.
|
|
32
34
|
impression_data[:pt] = @impression_observer.test_and_set(impression_data)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
+
end
|
|
36
|
+
if check_impression_counter(impression_data)
|
|
37
|
+
# In OPTIMIZED mode we should track the total amount of evaluations and deduplicate the impressions.
|
|
38
|
+
@impression_counter.inc(split_name, impression_data[:m])
|
|
35
39
|
end
|
|
36
40
|
rescue StandardError => e
|
|
37
41
|
@config.log_found_exception(__method__.to_s, e)
|
|
@@ -40,29 +44,50 @@ module SplitIoClient
|
|
|
40
44
|
impression(impression_data, params[:attributes])
|
|
41
45
|
end
|
|
42
46
|
|
|
43
|
-
def track(
|
|
44
|
-
return if
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
def track(impressions_decorator)
|
|
48
|
+
return if impressions_decorator.empty?
|
|
49
|
+
|
|
50
|
+
impressions_decorator.each do |impression_decorator|
|
|
51
|
+
impression_router.add_bulk([impression_decorator[:impression]])
|
|
52
|
+
stats = { dropped: 0, queued: 0, dedupe: 0 }
|
|
53
|
+
begin
|
|
54
|
+
next if @config.impressions_mode == :none || impression_decorator[:disabled]
|
|
55
|
+
|
|
56
|
+
if @config.impressions_mode == :debug
|
|
57
|
+
track_debug_mode([impression_decorator[:impression]], stats)
|
|
58
|
+
else
|
|
59
|
+
track_optimized_mode([impression_decorator[:impression]], stats)
|
|
60
|
+
end
|
|
61
|
+
rescue StandardError => e
|
|
62
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
63
|
+
ensure
|
|
64
|
+
record_stats(stats)
|
|
55
65
|
end
|
|
56
|
-
rescue StandardError => e
|
|
57
|
-
@config.log_found_exception(__method__.to_s, e)
|
|
58
|
-
ensure
|
|
59
|
-
record_stats(stats)
|
|
60
|
-
impression_router.add_bulk(impressions)
|
|
61
66
|
end
|
|
62
67
|
end
|
|
63
68
|
|
|
64
69
|
private
|
|
65
70
|
|
|
71
|
+
def check_return_conditions(properties)
|
|
72
|
+
return (@config.impressions_mode == :debug || @config.impressions_mode == :optimized) && !properties.nil?
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def check_none_mode(impressions_disabled)
|
|
76
|
+
return @config.impressions_mode == :none || impressions_disabled
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def check_observe_impressions
|
|
80
|
+
return @config.impressions_mode == :debug || @config.impressions_mode == :optimized
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def check_impression_counter(impression_data)
|
|
84
|
+
return @config.impressions_mode == :optimized && !impression_data[:pt].nil?
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def get_properties(evaluation_options)
|
|
88
|
+
return evaluation_options.nil? ? nil : evaluation_options.properties
|
|
89
|
+
end
|
|
90
|
+
|
|
66
91
|
def impression_router
|
|
67
92
|
@impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
|
|
68
93
|
rescue StandardError => e
|
|
@@ -81,7 +106,8 @@ module SplitIoClient
|
|
|
81
106
|
end
|
|
82
107
|
|
|
83
108
|
# added param time for test
|
|
84
|
-
def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil
|
|
109
|
+
def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil,
|
|
110
|
+
properties = nil)
|
|
85
111
|
{
|
|
86
112
|
k: matching_key,
|
|
87
113
|
b: bucketing_key,
|
|
@@ -90,7 +116,8 @@ module SplitIoClient
|
|
|
90
116
|
r: applied_rule(treatment[:label]),
|
|
91
117
|
c: treatment[:change_number],
|
|
92
118
|
m: time || (Time.now.to_f * 1000.0).to_i,
|
|
93
|
-
pt: nil
|
|
119
|
+
pt: nil,
|
|
120
|
+
properties: properties
|
|
94
121
|
}
|
|
95
122
|
end
|
|
96
123
|
|
|
@@ -126,11 +153,10 @@ module SplitIoClient
|
|
|
126
153
|
|
|
127
154
|
def track_optimized_mode(impressions, stats)
|
|
128
155
|
optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
|
|
129
|
-
|
|
156
|
+
stats[:dedupe] = impressions.length - optimized_impressions.length
|
|
130
157
|
return if optimized_impressions.empty?
|
|
131
158
|
|
|
132
159
|
stats[:dropped] = @impressions_repository.add_bulk(optimized_impressions)
|
|
133
|
-
stats[:dedupe] = impressions.length - optimized_impressions.length
|
|
134
160
|
stats[:queued] = optimized_impressions.length - stats[:dropped]
|
|
135
161
|
end
|
|
136
162
|
end
|
|
@@ -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
|
|
@@ -0,0 +1,194 @@
|
|
|
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 if @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
|
+
@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)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def unregister(sdk_event)
|
|
34
|
+
return unless @active_subscriptions.key?(sdk_event)
|
|
35
|
+
|
|
36
|
+
@mutex.synchronize do
|
|
37
|
+
@active_subscriptions.delete(sdk_event)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def notify_internal_event(sdk_internal_event, event_metadata)
|
|
42
|
+
@mutex.synchronize do
|
|
43
|
+
update_internal_event_status(sdk_internal_event, true)
|
|
44
|
+
@manager_config.evaluation_order.each do |sorted_event|
|
|
45
|
+
if get_sdk_event_if_applicable(sdk_internal_event).include?(sorted_event) &&
|
|
46
|
+
!get_event_handler(sorted_event).nil?
|
|
47
|
+
fire_sdk_event(sorted_event, event_metadata)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# if client is not subscribed to SDK_READY
|
|
51
|
+
if check_if_register_needed(sorted_event)
|
|
52
|
+
@config.logger.debug('EventsManager: Registering SDK_READY event as fired') if @config.debug_enabled
|
|
53
|
+
@active_subscriptions[Engine::Models::SdkEvent::SDK_READY] = Engine::Models::EventActiveSubscriptions.new(true, nil)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def destroy
|
|
60
|
+
@mutex.synchronize do
|
|
61
|
+
@active_subscriptions = {}
|
|
62
|
+
@internal_events_status = {}
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def check_if_register_needed(sorted_event)
|
|
69
|
+
sorted_event == SplitIoClient::Engine::Models::SdkEvent::SDK_READY &&
|
|
70
|
+
get_event_handler(sorted_event).nil? &&
|
|
71
|
+
!@active_subscriptions.include?(sorted_event)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def fire_sdk_event(sdk_event, event_metadata)
|
|
75
|
+
@config.logger.debug("EventsManager: Firing Sdk event: #{sdk_event}") if @config.debug_enabled
|
|
76
|
+
@config.threads[:sdk_event_notify] = Thread.new do
|
|
77
|
+
@events_delivery.deliver(sdk_event, event_metadata, get_event_handler(sdk_event))
|
|
78
|
+
end
|
|
79
|
+
sdk_event_triggered(sdk_event)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def event_already_triggered(sdk_event)
|
|
83
|
+
return @active_subscriptions[sdk_event].triggered if @active_subscriptions.key?(sdk_event)
|
|
84
|
+
|
|
85
|
+
false
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def get_internal_event_status(sdk_internal_event)
|
|
89
|
+
return @internal_events_status[sdk_internal_event] if @internal_events_status.key?(sdk_internal_event)
|
|
90
|
+
|
|
91
|
+
false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def update_internal_event_status(sdk_internal_event, status)
|
|
95
|
+
@internal_events_status[sdk_internal_event] = status
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def sdk_event_triggered(sdk_event)
|
|
99
|
+
return unless @active_subscriptions.key?(sdk_event)
|
|
100
|
+
|
|
101
|
+
return if @active_subscriptions[sdk_event].triggered
|
|
102
|
+
|
|
103
|
+
@active_subscriptions[sdk_event].triggered = true
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def get_event_handler(sdk_event)
|
|
107
|
+
return nil unless @active_subscriptions.key?(sdk_event)
|
|
108
|
+
|
|
109
|
+
@active_subscriptions[sdk_event].handler
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def get_sdk_event_if_applicable(sdk_internal_event)
|
|
113
|
+
final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false)
|
|
114
|
+
|
|
115
|
+
events_to_fire = []
|
|
116
|
+
require_any_sdk_event = check_require_any(sdk_internal_event)
|
|
117
|
+
if require_any_sdk_event.valid
|
|
118
|
+
if (!event_already_triggered(require_any_sdk_event.sdk_event) &&
|
|
119
|
+
execution_limit(require_any_sdk_event.sdk_event) == 1) ||
|
|
120
|
+
execution_limit(require_any_sdk_event.sdk_event) == -1
|
|
121
|
+
final_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(
|
|
122
|
+
require_any_sdk_event.sdk_event,
|
|
123
|
+
check_prerequisites(require_any_sdk_event.sdk_event) &&
|
|
124
|
+
check_suppressed_by(require_any_sdk_event.sdk_event)
|
|
125
|
+
)
|
|
126
|
+
end
|
|
127
|
+
events_to_fire.push(final_sdk_event.sdk_event) if final_sdk_event.valid
|
|
128
|
+
end
|
|
129
|
+
check_require_all.each { |sdk_event| events_to_fire.push(sdk_event) }
|
|
130
|
+
|
|
131
|
+
events_to_fire
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def check_require_all
|
|
135
|
+
events = []
|
|
136
|
+
@manager_config.require_all.each do |require_name, require_value|
|
|
137
|
+
final_status = true
|
|
138
|
+
require_value.each { |val| final_status &= get_internal_event_status(val) }
|
|
139
|
+
events.push(require_name) if check_event_eligible_conditions(final_status, require_name, require_value)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
events
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def check_event_eligible_conditions(final_status, require_name, require_value)
|
|
146
|
+
final_status &&
|
|
147
|
+
check_prerequisites(require_name) &&
|
|
148
|
+
((!event_already_triggered(require_name) &&
|
|
149
|
+
execution_limit(require_name) == 1) ||
|
|
150
|
+
execution_limit(require_name) == -1) &&
|
|
151
|
+
require_value.length.positive?
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def check_prerequisites(sdk_event)
|
|
155
|
+
@manager_config.prerequisites.each do |name, value|
|
|
156
|
+
value.each do |val|
|
|
157
|
+
return false if name == sdk_event && !event_already_triggered(val)
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
true
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def check_suppressed_by(sdk_event)
|
|
165
|
+
@manager_config.suppressed_by.each do |name, value|
|
|
166
|
+
value.each do |val|
|
|
167
|
+
return false if name == sdk_event && event_already_triggered(val)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
true
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def execution_limit(sdk_event)
|
|
175
|
+
return -1 unless @manager_config.execution_limits.key?(sdk_event)
|
|
176
|
+
|
|
177
|
+
@manager_config.execution_limits[sdk_event]
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def check_require_any(sdk_internal_event)
|
|
181
|
+
valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(nil, false)
|
|
182
|
+
@manager_config.require_any.each do |name, val|
|
|
183
|
+
if val.include?(sdk_internal_event)
|
|
184
|
+
valid_sdk_event = SplitIoClient::Engine::Models::ValidSdkEvent.new(name, true)
|
|
185
|
+
return valid_sdk_event
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
valid_sdk_event
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Engine
|
|
5
|
+
module Events
|
|
6
|
+
class EventsManagerConfig
|
|
7
|
+
attr_accessor :require_all, :prerequisites, :require_any, :suppressed_by, :execution_limits, :evaluation_order
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@require_all = construct_require_all
|
|
11
|
+
@prerequisites = construct_prerequisites
|
|
12
|
+
@require_any = construct_require_any
|
|
13
|
+
@suppressed_by = construct_suppressed_by
|
|
14
|
+
@execution_limits = construct_execution_limits
|
|
15
|
+
@evaluation_order = construct_sorted_events
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def construct_require_all
|
|
21
|
+
{
|
|
22
|
+
SplitIoClient::Engine::Models::SdkEvent::SDK_READY => Set.new([SplitIoClient::Engine::Models::SdkInternalEvent::SDK_READY])
|
|
23
|
+
}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def construct_prerequisites
|
|
27
|
+
{
|
|
28
|
+
SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new([SplitIoClient::Engine::Models::SdkEvent::SDK_READY])
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def construct_require_any
|
|
33
|
+
{
|
|
34
|
+
SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => Set.new(
|
|
35
|
+
[
|
|
36
|
+
SplitIoClient::Engine::Models::SdkInternalEvent::FLAG_KILLED_NOTIFICATION,
|
|
37
|
+
SplitIoClient::Engine::Models::SdkInternalEvent::FLAGS_UPDATED,
|
|
38
|
+
SplitIoClient::Engine::Models::SdkInternalEvent::RB_SEGMENTS_UPDATED,
|
|
39
|
+
SplitIoClient::Engine::Models::SdkInternalEvent::SEGMENTS_UPDATED
|
|
40
|
+
]
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def construct_suppressed_by
|
|
46
|
+
{}
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def construct_execution_limits
|
|
50
|
+
{
|
|
51
|
+
SplitIoClient::Engine::Models::SdkEvent::SDK_READY => 1,
|
|
52
|
+
SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE => -1
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def construct_sorted_events
|
|
57
|
+
sorted_events = []
|
|
58
|
+
[SplitIoClient::Engine::Models::SdkEvent::SDK_READY, SplitIoClient::Engine::Models::SdkEvent::SDK_UPDATE].each do |sdk_event|
|
|
59
|
+
sorted_events = dfs_recursive(sdk_event, sorted_events)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
sorted_events
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def dfs_recursive(sdk_event, added)
|
|
66
|
+
return added if added.include?(sdk_event)
|
|
67
|
+
|
|
68
|
+
get_dependencies(sdk_event).each do |dependent_event|
|
|
69
|
+
added = dfs_recursive(dependent_event, added)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
added.push(sdk_event)
|
|
73
|
+
|
|
74
|
+
added
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def get_dependencies(sdk_event)
|
|
78
|
+
dependencies = Set.new
|
|
79
|
+
@prerequisites.each do |prerequisites_event_name, prerequisites_event_value|
|
|
80
|
+
next unless prerequisites_event_name == sdk_event
|
|
81
|
+
|
|
82
|
+
prerequisites_event_value.each do |prereq_event|
|
|
83
|
+
dependencies.add(prereq_event)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
@suppressed_by.each do |suppressed_event_name, suppressed_event_value|
|
|
88
|
+
dependencies.add(suppressed_event_name) if suppressed_event_value.include?(sdk_event)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
dependencies
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Engine
|
|
5
|
+
module Events
|
|
6
|
+
class EventsTask
|
|
7
|
+
attr_accessor :running
|
|
8
|
+
|
|
9
|
+
def initialize(notify_internal_events, internal_events_queue, config)
|
|
10
|
+
@notify_internal_events = notify_internal_events
|
|
11
|
+
@internal_events_queue = internal_events_queue
|
|
12
|
+
@config = config
|
|
13
|
+
@running = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def start
|
|
17
|
+
return if @running
|
|
18
|
+
|
|
19
|
+
@config.logger.info('Starting Internal Events Task.')
|
|
20
|
+
@running = true
|
|
21
|
+
@config.threads[:internal_events_task] = Thread.new do
|
|
22
|
+
worker_thread
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def stop
|
|
27
|
+
return unless @running
|
|
28
|
+
|
|
29
|
+
@config.logger.info('Stopping Internal Events Task.')
|
|
30
|
+
@running = false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def worker_thread
|
|
36
|
+
while (event = @internal_events_queue.pop)
|
|
37
|
+
break unless @running
|
|
38
|
+
|
|
39
|
+
@config.logger.debug("Processing sdk internal event: #{event.internal_event}") if @config.debug_enabled
|
|
40
|
+
begin
|
|
41
|
+
@notify_internal_events.call(event.internal_event, event.metadata)
|
|
42
|
+
rescue StandardError => e
|
|
43
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|