splitclient-rb 1.0.2.wip → 8.11.1.pre.rc1-java

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.
Files changed (210) hide show
  1. checksums.yaml +5 -5
  2. data/.github/CODEOWNERS +1 -0
  3. data/.github/pull_request_template.md +9 -0
  4. data/.github/workflows/ci.yml +90 -0
  5. data/.github/workflows/update-license-year.yml +45 -0
  6. data/.gitignore +20 -0
  7. data/.rubocop.yml +76 -0
  8. data/.simplecov +1 -0
  9. data/CHANGES.txt +361 -2
  10. data/CONTRIBUTORS-GUIDE.md +49 -0
  11. data/Gemfile +2 -0
  12. data/LICENSE +3 -36
  13. data/NOTICE.txt +5 -0
  14. data/README.md +59 -127
  15. data/Rakefile +27 -3
  16. data/ext/murmurhash/MurmurHash3.java +302 -0
  17. data/gemfiles/faraday_after_0.13.gemfile +7 -0
  18. data/gemfiles/faraday_before_0.13.gemfile +8 -0
  19. data/lib/murmurhash/base.rb +58 -0
  20. data/lib/murmurhash/murmurhash.jar +0 -0
  21. data/lib/murmurhash/murmurhash_mri.rb +3 -0
  22. data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +130 -0
  23. data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
  24. data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +137 -0
  25. data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +53 -0
  26. data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +178 -0
  27. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +83 -0
  28. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +70 -0
  29. data/lib/splitclient-rb/cache/filter/bloom_filter.rb +67 -0
  30. data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
  31. data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
  32. data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
  33. data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
  34. data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
  35. data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +45 -0
  36. data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +35 -0
  37. data/lib/splitclient-rb/cache/repositories/events_repository.rb +61 -0
  38. data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
  39. data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
  40. data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +56 -0
  41. data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +57 -0
  42. data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +23 -0
  43. data/lib/splitclient-rb/cache/repositories/repository.rb +24 -0
  44. data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
  45. data/lib/splitclient-rb/cache/repositories/segments_repository.rb +124 -0
  46. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +296 -0
  47. data/lib/splitclient-rb/cache/routers/impression_router.rb +60 -0
  48. data/lib/splitclient-rb/cache/senders/events_sender.rb +42 -0
  49. data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
  50. data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +69 -0
  51. data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +43 -0
  52. data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +95 -0
  53. data/lib/splitclient-rb/cache/senders/impressions_sender.rb +52 -0
  54. data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
  55. data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +47 -0
  56. data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +95 -0
  57. data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +110 -0
  58. data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
  59. data/lib/splitclient-rb/clients/split_client.rb +530 -0
  60. data/lib/splitclient-rb/constants.rb +16 -0
  61. data/lib/splitclient-rb/engine/api/client.rb +87 -0
  62. data/lib/splitclient-rb/engine/api/events.rb +57 -0
  63. data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +58 -0
  64. data/lib/splitclient-rb/engine/api/impressions.rb +79 -0
  65. data/lib/splitclient-rb/engine/api/segments.rb +78 -0
  66. data/lib/splitclient-rb/engine/api/splits.rb +139 -0
  67. data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
  68. data/lib/splitclient-rb/engine/auth_api_client.rb +100 -0
  69. data/lib/splitclient-rb/engine/back_off.rb +26 -0
  70. data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
  71. data/lib/splitclient-rb/engine/common/impressions_manager.rb +165 -0
  72. data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
  73. data/lib/{splitclient-engine → splitclient-rb/engine}/evaluator/splitter.rb +35 -22
  74. data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
  75. data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
  76. data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
  77. data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
  78. data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
  79. data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
  80. data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
  81. data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +144 -0
  82. data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/all_keys_matcher.rb +10 -21
  83. data/lib/splitclient-rb/engine/matchers/between_matcher.rb +47 -0
  84. data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
  85. data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/combiners.rb +4 -6
  86. data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +80 -0
  87. data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +20 -0
  88. data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +16 -0
  89. data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +43 -0
  90. data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +26 -0
  91. data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +42 -0
  92. data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +37 -0
  93. data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +44 -0
  94. data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
  95. data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +16 -0
  96. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +43 -0
  97. data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
  98. data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
  99. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +43 -0
  100. data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
  101. data/lib/splitclient-rb/engine/matchers/matcher.rb +52 -0
  102. data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +29 -0
  103. data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +47 -0
  104. data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +22 -0
  105. data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
  106. data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
  107. data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
  108. data/lib/splitclient-rb/engine/matchers/set_matcher.rb +27 -0
  109. data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +40 -0
  110. data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +28 -0
  111. data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
  112. data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +66 -0
  113. data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
  114. data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
  115. data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
  116. data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
  117. data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
  118. data/lib/splitclient-rb/engine/models/label.rb +10 -0
  119. data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
  120. data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
  121. data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
  122. data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +16 -0
  123. data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
  124. data/lib/splitclient-rb/engine/models/split.rb +17 -0
  125. data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
  126. data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
  127. data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
  128. data/lib/splitclient-rb/engine/parser/condition.rb +271 -0
  129. data/lib/splitclient-rb/engine/parser/evaluator.rb +112 -0
  130. data/lib/{splitclient-engine → splitclient-rb/engine}/parser/partition.rb +2 -4
  131. data/lib/splitclient-rb/engine/push_manager.rb +66 -0
  132. data/lib/splitclient-rb/engine/status_manager.rb +39 -0
  133. data/lib/splitclient-rb/engine/sync_manager.rb +183 -0
  134. data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
  135. data/lib/splitclient-rb/exceptions.rb +26 -0
  136. data/lib/splitclient-rb/helpers/decryption_helper.rb +25 -0
  137. data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
  138. data/lib/splitclient-rb/helpers/repository_helper.rb +61 -0
  139. data/lib/splitclient-rb/helpers/thread_helper.rb +24 -0
  140. data/lib/splitclient-rb/helpers/util.rb +26 -0
  141. data/lib/splitclient-rb/managers/split_manager.rb +121 -0
  142. data/lib/splitclient-rb/spec.rb +9 -0
  143. data/lib/splitclient-rb/split_config.rb +591 -47
  144. data/lib/splitclient-rb/split_factory.rb +284 -0
  145. data/lib/splitclient-rb/split_factory_builder.rb +7 -0
  146. data/lib/splitclient-rb/split_factory_registry.rb +63 -0
  147. data/lib/splitclient-rb/split_logger.rb +23 -0
  148. data/lib/splitclient-rb/sse/event_source/client.rb +264 -0
  149. data/lib/splitclient-rb/sse/event_source/event_parser.rb +65 -0
  150. data/lib/splitclient-rb/sse/event_source/event_types.rb +15 -0
  151. data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
  152. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +84 -0
  153. data/lib/splitclient-rb/sse/notification_processor.rb +48 -0
  154. data/lib/splitclient-rb/sse/sse_handler.rb +44 -0
  155. data/lib/splitclient-rb/sse/workers/segments_worker.rb +62 -0
  156. data/lib/splitclient-rb/sse/workers/splits_worker.rb +153 -0
  157. data/lib/splitclient-rb/telemetry/domain/constants.rb +48 -0
  158. data/lib/splitclient-rb/telemetry/domain/structs.rb +35 -0
  159. data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
  160. data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
  161. data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
  162. data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
  163. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
  164. data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
  165. data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
  166. data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
  167. data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +119 -0
  168. data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +87 -0
  169. data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +213 -0
  170. data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
  171. data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
  172. data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +27 -0
  173. data/lib/splitclient-rb/telemetry/runtime_consumer.rb +25 -0
  174. data/lib/splitclient-rb/telemetry/runtime_producer.rb +25 -0
  175. data/lib/splitclient-rb/telemetry/storages/memory.rb +159 -0
  176. data/lib/splitclient-rb/telemetry/sync_task.rb +36 -0
  177. data/lib/splitclient-rb/telemetry/synchronizer.rb +33 -0
  178. data/lib/splitclient-rb/utilitites.rb +49 -0
  179. data/lib/splitclient-rb/validators.rb +361 -0
  180. data/lib/splitclient-rb/version.rb +1 -1
  181. data/lib/splitclient-rb.rb +177 -25
  182. data/sonar-project.properties +6 -0
  183. data/splitclient-rb.gemspec +53 -26
  184. data/tasks/benchmark_get_treatment.rake +31 -17
  185. data/tasks/irb.rake +6 -0
  186. metadata +411 -86
  187. data/NEWS +0 -9
  188. data/lib/splitclient-cache/local_store.rb +0 -45
  189. data/lib/splitclient-engine/impressions/impressions.rb +0 -79
  190. data/lib/splitclient-engine/matchers/between_matcher.rb +0 -48
  191. data/lib/splitclient-engine/matchers/combining_matcher.rb +0 -94
  192. data/lib/splitclient-engine/matchers/equal_to_matcher.rb +0 -48
  193. data/lib/splitclient-engine/matchers/greater_than_or_equal_to_matcher.rb +0 -48
  194. data/lib/splitclient-engine/matchers/less_than_or_equal_to_matcher.rb +0 -48
  195. data/lib/splitclient-engine/matchers/negation_matcher.rb +0 -54
  196. data/lib/splitclient-engine/matchers/user_defined_segment_matcher.rb +0 -61
  197. data/lib/splitclient-engine/matchers/whitelist_matcher.rb +0 -64
  198. data/lib/splitclient-engine/metrics/binary_search_latency_tracker.rb +0 -122
  199. data/lib/splitclient-engine/metrics/metrics.rb +0 -158
  200. data/lib/splitclient-engine/parser/condition.rb +0 -143
  201. data/lib/splitclient-engine/parser/segment.rb +0 -84
  202. data/lib/splitclient-engine/parser/segment_parser.rb +0 -46
  203. data/lib/splitclient-engine/parser/split.rb +0 -68
  204. data/lib/splitclient-engine/parser/split_adapter.rb +0 -433
  205. data/lib/splitclient-engine/parser/split_parser.rb +0 -139
  206. data/lib/splitclient-engine/partitions/treatments.rb +0 -40
  207. data/lib/splitclient-rb/split_client.rb +0 -156
  208. data/lib/splitclient-rb_utilitites.rb +0 -26
  209. data/tasks/console.rake +0 -4
  210. data/tasks/rspec.rake +0 -3
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Common
6
+ class ImpressionManager
7
+ def initialize(config,
8
+ impressions_repository,
9
+ impression_counter,
10
+ telemetry_runtime_producer,
11
+ impression_observer,
12
+ unique_keys_tracker)
13
+ @config = config
14
+ @impressions_repository = impressions_repository
15
+ @impression_counter = impression_counter
16
+ @impression_observer = impression_observer
17
+ @telemetry_runtime_producer = telemetry_runtime_producer
18
+ @unique_keys_tracker = unique_keys_tracker
19
+ end
20
+
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)
26
+
27
+ begin
28
+ if check_none_mode(impressions_disabled)
29
+ @impression_counter.inc(split_name, impression_data[:m])
30
+ @unique_keys_tracker.track(split_name, matching_key)
31
+ end
32
+ if check_observe_impressions
33
+ # In DEBUG mode we should calculate the pt only.
34
+ impression_data[:pt] = @impression_observer.test_and_set(impression_data)
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])
39
+ end
40
+ rescue StandardError => e
41
+ @config.log_found_exception(__method__.to_s, e)
42
+ end
43
+
44
+ impression(impression_data, params[:attributes])
45
+ end
46
+
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)
65
+ end
66
+ end
67
+ end
68
+
69
+ private
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
+
91
+ def impression_router
92
+ @impression_router ||= SplitIoClient::ImpressionRouter.new(@config)
93
+ rescue StandardError => e
94
+ @config.log_found_exception(__method__.to_s, e)
95
+ end
96
+
97
+ def record_stats(stats)
98
+ return if redis?
99
+
100
+ imp_queued = Telemetry::Domain::Constants::IMPRESSIONS_QUEUED
101
+ imp_dropped = Telemetry::Domain::Constants::IMPRESSIONS_DROPPED
102
+ imp_dedupe = Telemetry::Domain::Constants::IMPRESSIONS_DEDUPE
103
+ @telemetry_runtime_producer.record_impressions_stats(imp_queued, stats[:queued]) unless stats[:queued].zero?
104
+ @telemetry_runtime_producer.record_impressions_stats(imp_dropped, stats[:dropped]) unless stats[:dropped].zero?
105
+ @telemetry_runtime_producer.record_impressions_stats(imp_dedupe, stats[:dedupe]) unless stats[:dedupe].zero?
106
+ end
107
+
108
+ # added param time for test
109
+ def impression_data(matching_key, bucketing_key, split_name, treatment, time = nil,
110
+ properties = nil)
111
+ {
112
+ k: matching_key,
113
+ b: bucketing_key,
114
+ f: split_name,
115
+ t: treatment[:treatment],
116
+ r: applied_rule(treatment[:label]),
117
+ c: treatment[:change_number],
118
+ m: time || (Time.now.to_f * 1000.0).to_i,
119
+ pt: nil,
120
+ properties: properties
121
+ }
122
+ end
123
+
124
+ def metadata
125
+ {
126
+ s: "#{@config.language}-#{@config.version}",
127
+ i: @config.machine_ip,
128
+ n: @config.machine_name
129
+ }
130
+ end
131
+
132
+ def applied_rule(label)
133
+ @config.labels_enabled ? label : nil
134
+ end
135
+
136
+ def should_queue_impression?(impression)
137
+ impression[:pt].nil? ||
138
+ (ImpressionCounter.truncate_time_frame(impression[:pt]) != ImpressionCounter.truncate_time_frame(impression[:m]))
139
+ end
140
+
141
+ def impression(impression_data, attributes)
142
+ { m: metadata, i: impression_data, attributes: attributes }
143
+ end
144
+
145
+ def redis?
146
+ @config.impressions_adapter.class.to_s == 'SplitIoClient::Cache::Adapters::RedisAdapter'
147
+ end
148
+
149
+ def track_debug_mode(impressions, stats)
150
+ stats[:dropped] = @impressions_repository.add_bulk(impressions)
151
+ stats[:queued] = impressions.length - stats[:dropped]
152
+ end
153
+
154
+ def track_optimized_mode(impressions, stats)
155
+ optimized_impressions = impressions.select { |imp| should_queue_impression?(imp[:i]) }
156
+ stats[:dedupe] = impressions.length - optimized_impressions.length
157
+ return if optimized_impressions.empty?
158
+
159
+ stats[:dropped] = @impressions_repository.add_bulk(optimized_impressions)
160
+ stats[:queued] = optimized_impressions.length - stats[:dropped]
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+
5
+ module SplitIoClient
6
+ module Engine
7
+ module Common
8
+ class NoopImpressionCounter
9
+ def inc(split_name, time_frame)
10
+ # no-op
11
+ end
12
+
13
+ def pop_all
14
+ # no-op
15
+ end
16
+
17
+ def make_key(split_name, time_frame)
18
+ # no-op
19
+ end
20
+
21
+ def self.truncate_time_frame(timestamp_ms)
22
+ # no-op
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -4,21 +4,25 @@ module SplitIoClient
4
4
  # based on provided key
5
5
  #
6
6
  class Splitter < NoMethodError
7
-
7
+ def initialize
8
+ @murmur_hash = case RUBY_PLATFORM
9
+ when 'java'
10
+ Proc.new { |key, seed| Java::MurmurHash3.murmurhash3_x86_32(key, seed) }
11
+ else
12
+ Proc.new { |key, seed| Digest::MurmurHashMRI3_x86_32.rawdigest(key, [seed].pack('L')) }
13
+ end
14
+ end
15
+
8
16
  #
9
17
  # Checks if the partiotion size is 100%
10
18
  #
11
19
  # @param partitions [object] array of partitions
12
20
  #
13
21
  # @return [boolean] true if partition is 100% false otherwise
14
- def self.hundred_percent_one_treatment?(partitions)
15
- if partitions.size != 1
16
- return false
17
- end
18
- return (partitions.first).size == 100
22
+ def hundred_percent_one_treatment?(partitions)
23
+ (partitions.size != 1) ? false : (partitions.first.size == 100)
19
24
  end
20
25
 
21
-
22
26
  #
23
27
  # gets the appropriate treatment based on id, seed and partition value
24
28
  #
@@ -27,30 +31,41 @@ module SplitIoClient
27
31
  # @param partitions [object] array of partitions
28
32
  #
29
33
  # @return traetment [object] treatment value
30
- def self.get_treatment(id, seed, partitions)
34
+ def get_treatment(id, seed, partitions, legacy_algo)
35
+ legacy = [1, nil].include?(legacy_algo)
36
+
31
37
  if partitions.empty?
32
- return Treatments::CONTROL
38
+ return SplitIoClient::Engine::Models::Treatment::CONTROL
33
39
  end
34
40
 
35
41
  if hundred_percent_one_treatment?(partitions)
36
42
  return (partitions.first).treatment
37
43
  end
38
44
 
39
- return get_treatment_for_key(bucket(hash(id, seed)), partitions)
45
+ return get_treatment_for_key(bucket(count_hash(id, seed, legacy)), partitions)
40
46
  end
41
47
 
48
+ # returns a hash value for the give key, seed pair
42
49
  #
43
- # returns a hash value for the give key, sedd pair
44
- #
45
- # @param key [string] user key
46
- # @param seed [number] seed for the user key
50
+ # @param key [String] user key
51
+ # @param seed [Fixnum] seed for the user key
47
52
  #
48
- # @return hash [string] hash value
49
- def self.hash(key, seed)
53
+ # @return hash [String] hash value
54
+ def count_hash(key, seed, legacy)
55
+ legacy ? legacy_hash(key, seed) : murmur_hash(key, seed)
56
+ end
57
+
58
+ def murmur_hash(key, seed)
59
+ @murmur_hash.call(key, seed)
60
+ end
61
+
62
+ def legacy_hash(key, seed)
50
63
  h = 0
64
+
51
65
  for i in 0..key.length-1
52
66
  h = to_int32(31 * h + key[i].ord)
53
67
  end
68
+
54
69
  h^seed
55
70
  end
56
71
 
@@ -60,7 +75,7 @@ module SplitIoClient
60
75
  # @param number [number] ruby number value
61
76
  #
62
77
  # @return [int] returns the int 32 value of the provided number
63
- def self.to_int32(number)
78
+ def to_int32(number)
64
79
  begin
65
80
  sign = number < 0 ? -1 : 1
66
81
  abs = number.abs
@@ -83,7 +98,7 @@ module SplitIoClient
83
98
  # @param parittions [object] array of partitions
84
99
  #
85
100
  # @return treatment [treatment] treatment value for this bucket and partitions
86
- def self.get_treatment_for_key(bucket, partitions)
101
+ def get_treatment_for_key(bucket, partitions)
87
102
  buckets_covered_thus_far = 0
88
103
  partitions.each do |p|
89
104
  unless p.is_empty?
@@ -92,7 +107,7 @@ module SplitIoClient
92
107
  end
93
108
  end
94
109
 
95
- return Treatments::CONTROL
110
+ return SplitIoClient::Engine::Models::Treatment::CONTROL
96
111
  end
97
112
 
98
113
  #
@@ -101,10 +116,8 @@ module SplitIoClient
101
116
  # @param hash_value [string] hash value
102
117
  #
103
118
  # @return bucket [number] bucket number
104
- def self.bucket(hash_value)
119
+ def bucket(hash_value)
105
120
  (hash_value.abs % 100) + 1
106
121
  end
107
-
108
122
  end
109
-
110
123
  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 == Engine::Models::SdkEvent::SDK_READY && event_already_triggered(sdk_event)
22
+ @active_subscriptions[sdk_event] = 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] = 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 == 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 = 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 = 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 = 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 = 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
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ module Events
6
+ class NoOpEventsQueue
7
+ def push(sdk_event)
8
+ # do nothing
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end