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,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ class SplitFactory
5
+ ROOT_PROCESS_ID = Process.pid
6
+ SINGLETON_WARN = 'We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'
7
+ LOCALHOST_API_KEY = 'localhost'
8
+
9
+ include SplitIoClient::Cache::Repositories
10
+ include SplitIoClient::Cache::Stores
11
+ include SplitIoClient::Cache::Senders
12
+ include SplitIoClient::Cache::Fetchers
13
+
14
+ attr_reader :adapter, :client, :manager, :config
15
+
16
+ def initialize(api_key, config_hash = {})
17
+ at_exit do
18
+ unless ENV['SPLITCLIENT_ENV'] == 'test'
19
+ if (Process.pid == ROOT_PROCESS_ID)
20
+ @config.logger.info('Split SDK shutdown started...')
21
+ @client.destroy if @client
22
+ stop!
23
+ @config.logger.info('Split SDK shutdown complete')
24
+ end
25
+ end
26
+ end
27
+
28
+ @api_key = api_key
29
+
30
+ store_flag_sets = config_hash.key?(:flag_sets_filter) ? config_hash[:flag_sets_filter] : []
31
+ store_flag_sets = [] if !store_flag_sets.is_a?(Array)
32
+ flag_sets_count = 0
33
+ flag_sets_invalid = 0
34
+
35
+ @config = SplitConfig.new(config_hash.merge(localhost_mode: @api_key == LOCALHOST_API_KEY ))
36
+
37
+ if config_hash.key?(:flag_sets_filter)
38
+ flag_sets_count = store_flag_sets.length()
39
+ flag_sets_invalid = flag_sets_count - @config.flag_sets_filter.length()
40
+ end
41
+
42
+ raise 'Invalid SDK mode' unless valid_mode
43
+
44
+ validate_api_key
45
+
46
+ register_factory
47
+
48
+ build_events_manager
49
+ build_telemetry_components
50
+ build_flag_sets_filter
51
+ build_repositories
52
+ build_telemetry_synchronizer(flag_sets_count, flag_sets_invalid)
53
+ build_impressions_sender_adapter
54
+ build_unique_keys_tracker
55
+ build_impressions_components
56
+
57
+ @status_manager = Engine::StatusManager.new(@config, @internal_events_queue)
58
+ @split_validator = SplitIoClient::Validators.new(@config)
59
+ @evaluator = Engine::Parser::Evaluator.new(@segments_repository, @splits_repository, @rule_based_segment_repository, @config)
60
+
61
+ start!
62
+ fallback_treatment_calculator = SplitIoClient::Engine::FallbackTreatmentCalculator.new(@config.fallback_treatments_configuration)
63
+ @client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer, @evaluator, @split_validator, fallback_treatment_calculator, @events_manager)
64
+ @manager = SplitManager.new(@splits_repository, @status_manager, @config)
65
+ end
66
+
67
+ def start!
68
+ return start_localhost_components if @config.localhost_mode
69
+
70
+ if @config.consumer?
71
+ build_synchronizer
72
+ build_sync_manager
73
+
74
+ @sync_manager.start_consumer
75
+ return
76
+ end
77
+
78
+ build_fetchers
79
+ build_synchronizer
80
+ build_streaming_components
81
+ build_sync_manager
82
+
83
+ @sync_manager.start
84
+ end
85
+
86
+ def stop!
87
+ @config.threads.each { |_, t| t.exit }
88
+ end
89
+
90
+ def register_factory
91
+ SplitIoClient.load_factory_registry
92
+
93
+ number_of_factories = SplitIoClient.split_factory_registry.number_of_factories_for(@api_key)
94
+
95
+ if(number_of_factories > 0)
96
+ @config.logger.warn("Factory instantiation: You already have #{number_of_factories} factories with this API Key. #{SINGLETON_WARN}")
97
+ elsif(SplitIoClient.split_factory_registry.other_factories)
98
+ @config.logger.warn('Factory instantiation: You already have an instance of the Split factory.' \
99
+ " Make sure you definitely want this additional instance. #{SINGLETON_WARN}")
100
+ end
101
+
102
+ SplitIoClient.split_factory_registry.add(@api_key)
103
+ end
104
+
105
+ def valid_mode
106
+ valid_startup_mode = false
107
+ case @config.mode
108
+ when :consumer
109
+ if @config.cache_adapter.is_a? SplitIoClient::Cache::Adapters::RedisAdapter
110
+ if !@config.localhost_mode
111
+ valid_startup_mode = true
112
+ else
113
+ @config.logger.error('Localhost mode cannot be used with Redis. ' \
114
+ 'Use standalone mode and Memory adapter instead.')
115
+ end
116
+ else
117
+ @config.logger.error('Consumer mode cannot be used with Memory adapter. ' \
118
+ 'Use Redis adapter instead.')
119
+ end
120
+ when :standalone
121
+ if @config.cache_adapter.is_a? SplitIoClient::Cache::Adapters::MemoryAdapter
122
+ valid_startup_mode = true
123
+ else
124
+ @config.logger.error('Standalone mode cannot be used with Redis adapter. ' \
125
+ 'Use Memory adapter instead.')
126
+ end
127
+ when :producer
128
+ @config.logger.error('Producer mode is no longer supported. Use Split Synchronizer. ' \
129
+ 'See: https://github.com/splitio/split-synchronizer')
130
+ else
131
+ @config.logger.error('Invalid SDK mode selected. ' \
132
+ "Valid modes are 'standalone with memory adapter' and 'consumer with redis adapter'")
133
+ end
134
+
135
+ valid_startup_mode
136
+ end
137
+
138
+ alias resume! start!
139
+
140
+ private
141
+
142
+ def validate_api_key
143
+ if(@api_key.nil?)
144
+ @config.logger.error('Factory Instantiation: you passed a nil api_key, api_key must be a non-empty String')
145
+ @config.valid_mode = false
146
+ elsif (@api_key.empty?)
147
+ @config.logger.error('Factory Instantiation: you passed and empty api_key, api_key must be a non-empty String')
148
+ @config.valid_mode = false
149
+ end
150
+ end
151
+
152
+ def repositories
153
+ {
154
+ splits: @splits_repository,
155
+ segments: @segments_repository,
156
+ impressions: @impressions_repository,
157
+ events: @events_repository,
158
+ rule_based_segments: @rule_based_segment_repository
159
+ }
160
+ end
161
+
162
+ def start_localhost_components
163
+ LocalhostSplitStore.new(@splits_repository, @config, @status_manager).call
164
+
165
+ # Starts thread which loops constantly and cleans up repositories to avoid memory issues in localhost mode
166
+ LocalhostRepoCleaner.new(@impressions_repository, @events_repository, @config).call
167
+ end
168
+
169
+ def build_telemetry_components
170
+ @evaluation_consumer = Telemetry::EvaluationConsumer.new(@config)
171
+ @evaluation_producer = Telemetry::EvaluationProducer.new(@config)
172
+
173
+ @init_consumer = Telemetry::InitConsumer.new(@config)
174
+ @init_producer = Telemetry::InitProducer.new(@config)
175
+
176
+ @runtime_consumer = Telemetry::RuntimeConsumer.new(@config)
177
+ @runtime_producer = Telemetry::RuntimeProducer.new(@config)
178
+
179
+ @telemetry_consumers = { init: @init_consumer, evaluation: @evaluation_consumer, runtime: @runtime_consumer }
180
+ end
181
+
182
+ def build_fetchers
183
+ @split_fetcher = SplitFetcher.new(@splits_repository, @rule_based_segment_repository, @api_key, @config, @runtime_producer)
184
+ @segment_fetcher = SegmentFetcher.new(@segments_repository, @api_key, @config, @runtime_producer)
185
+ end
186
+
187
+ def build_synchronizer
188
+ params = {
189
+ split_fetcher: @split_fetcher,
190
+ segment_fetcher: @segment_fetcher,
191
+ imp_counter: @impression_counter,
192
+ telemetry_synchronizer: @telemetry_synchronizer,
193
+ impressions_sender_adapter: @impressions_sender_adapter,
194
+ impressions_api: @impressions_api,
195
+ unique_keys_tracker: @unique_keys_tracker
196
+ }
197
+
198
+ @synchronizer = Engine::Synchronizer.new(repositories, @config, params)
199
+ end
200
+
201
+ def build_streaming_components
202
+ @push_status_queue = Queue.new
203
+ splits_worker = SSE::Workers::SplitsWorker.new(@synchronizer, @config, @splits_repository, @runtime_producer, @segment_fetcher, @rule_based_segment_repository)
204
+ segments_worker = SSE::Workers::SegmentsWorker.new(@synchronizer, @config, @segments_repository)
205
+ notification_manager_keeper = SSE::NotificationManagerKeeper.new(@config, @runtime_producer, @push_status_queue)
206
+ notification_processor = SSE::NotificationProcessor.new(@config, splits_worker, segments_worker)
207
+ event_parser = SSE::EventSource::EventParser.new(config)
208
+ sse_client = SSE::EventSource::Client.new(@config, @api_key, @runtime_producer, event_parser, notification_manager_keeper, notification_processor, @push_status_queue)
209
+ @sse_handler = SSE::SSEHandler.new(@config, splits_worker, segments_worker, sse_client)
210
+ @push_manager = Engine::PushManager.new(@config, @sse_handler, @api_key, @runtime_producer)
211
+ end
212
+
213
+ def build_sync_manager
214
+ @sync_manager = Engine::SyncManager.new(@config, @synchronizer, @runtime_producer, @telemetry_synchronizer, @status_manager, @sse_handler, @push_manager, @push_status_queue)
215
+ end
216
+
217
+ def build_repositories
218
+ if @config.cache_adapter.is_a? SplitIoClient::Cache::Adapters::RedisAdapter
219
+ @flag_sets_repository = SplitIoClient::Cache::Repositories::RedisFlagSetsRepository.new(@config)
220
+ else
221
+ @flag_sets_repository = SplitIoClient::Cache::Repositories::MemoryFlagSetsRepository.new(@config.flag_sets_filter)
222
+ end
223
+ @splits_repository = SplitsRepository.new(@config, @flag_sets_repository, @flag_sets_filter, @internal_events_queue)
224
+ @segments_repository = SegmentsRepository.new(@config, @internal_events_queue)
225
+ @rule_based_segment_repository = RuleBasedSegmentsRepository.new(@config, @internal_events_queue)
226
+ @impressions_repository = ImpressionsRepository.new(@config)
227
+ @events_repository = EventsRepository.new(@config, @api_key, @runtime_producer)
228
+ end
229
+
230
+ def build_telemetry_synchronizer(flag_sets, flag_sets_invalid)
231
+ @telemetry_api = Api::TelemetryApi.new(@config, @api_key, @runtime_producer)
232
+ @telemetry_synchronizer = Telemetry::Synchronizer.new(@config, @telemetry_consumers, @init_producer, repositories, @telemetry_api, flag_sets, flag_sets_invalid)
233
+ end
234
+
235
+ def build_unique_keys_tracker
236
+ bf = Cache::Filter::BloomFilter.new(30_000_000)
237
+ filter_adapter = Cache::Filter::FilterAdapter.new(@config, bf)
238
+ cache = Concurrent::Hash.new
239
+ @unique_keys_tracker = Engine::Impressions::UniqueKeysTracker.new(@config, filter_adapter, @impressions_sender_adapter, cache)
240
+ end
241
+
242
+ def build_impressions_observer
243
+ if (@config.cache_adapter == :redis && @config.impressions_mode != :optimized)
244
+ @impression_observer = Observers::NoopImpressionObserver.new
245
+ else
246
+ @impression_observer = Observers::ImpressionObserver.new
247
+ end
248
+ end
249
+
250
+ def build_impression_counter
251
+ @impression_counter = Engine::Common::ImpressionCounter.new
252
+ end
253
+
254
+ def build_impressions_sender_adapter
255
+ @impressions_api = Api::Impressions.new(@api_key, @config, @runtime_producer)
256
+ @impressions_sender_adapter = Cache::Senders::ImpressionsSenderAdapter.new(@config, @telemetry_api, @impressions_api)
257
+ end
258
+
259
+ def build_impressions_components
260
+ build_impressions_observer
261
+ build_impression_counter
262
+
263
+ @impressions_manager = Engine::Common::ImpressionManager.new(@config, @impressions_repository, @impression_counter, @runtime_producer, @impression_observer, @unique_keys_tracker)
264
+ end
265
+
266
+ def build_flag_sets_filter
267
+ @flag_sets_filter = SplitIoClient::Cache::Filter::FlagSetsFilter.new(@config.flag_sets_filter)
268
+ end
269
+
270
+ def build_events_manager
271
+ @events_manager = Engine::Events::EventsManager.new(Engine::Events::EventsManagerConfig.new,
272
+ Engine::Events::EventsDelivery.new(@config),
273
+ @config)
274
+ if @config.consumer?
275
+ @internal_events_queue = Engine::Events::NoOpEventsQueue.new
276
+ return
277
+ end
278
+
279
+ @internal_events_queue = Queue.new
280
+ @events_task = Engine::Events::EventsTask.new(@events_manager.method(:notify_internal_event), @internal_events_queue, @config)
281
+ @events_task.start
282
+ end
283
+ end
284
+ end
@@ -0,0 +1,7 @@
1
+ module SplitIoClient
2
+ class SplitFactoryBuilder
3
+ def self.build(api_key, config = {})
4
+ SplitFactory.new(api_key, config)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,63 @@
1
+ require 'logger'
2
+ require 'socket'
3
+
4
+ module SplitIoClient
5
+
6
+ class << self
7
+ attr_accessor :split_factory_registry
8
+ end
9
+
10
+ def self.load_factory_registry
11
+ self.split_factory_registry ||= SplitFactoryRegistry.new
12
+ end
13
+
14
+ #
15
+ # This class manages configuration options for the split client library.
16
+ # If not custom configuration is required the default configuration values will be used
17
+ #
18
+ class SplitFactoryRegistry
19
+
20
+ attr_accessor :api_keys_hash
21
+
22
+ def initialize
23
+ @api_keys_hash = Hash.new
24
+ end
25
+
26
+ def add(api_key)
27
+ return unless api_key
28
+
29
+ @api_keys_hash[api_key] = 0 unless @api_keys_hash[api_key]
30
+ @api_keys_hash[api_key] += 1
31
+ end
32
+
33
+ def remove(api_key)
34
+ return unless api_key
35
+
36
+ @api_keys_hash[api_key] -= 1 if @api_keys_hash[api_key]
37
+ @api_keys_hash.delete(api_key) if @api_keys_hash[api_key] == 0
38
+ end
39
+
40
+ def number_of_factories_for(api_key)
41
+ return 0 unless api_key
42
+ return 0 unless @api_keys_hash.key?(api_key)
43
+
44
+ @api_keys_hash[api_key]
45
+ end
46
+
47
+ def other_factories
48
+ return !@api_keys_hash.empty?
49
+ end
50
+
51
+ def active_factories
52
+ @api_keys_hash.length
53
+ end
54
+
55
+ def redundant_active_factories
56
+ to_return = 0
57
+
58
+ @api_keys_hash.each { |key| to_return += (key[1]-1) }
59
+
60
+ to_return
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ module SplitIoClient
2
+ class SplitLogger
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ def log_if_debug(message)
8
+ @config.logger.debug(message) if @config.debug_enabled
9
+ end
10
+
11
+ def log_if_transport(message)
12
+ @config.logger.debug(message) if @config.transport_debug_enabled
13
+ end
14
+
15
+ def error(message)
16
+ @config.logger.error(message)
17
+ end
18
+
19
+ def debug(message)
20
+ @config.logger.debug(message) if @config.debug_enabled
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: false
2
+
3
+ require 'socket'
4
+ require 'openssl'
5
+ require 'uri'
6
+ require 'timeout'
7
+
8
+ module SplitIoClient
9
+ module SSE
10
+ module EventSource
11
+ class Client
12
+ DEFAULT_READ_TIMEOUT = 70
13
+ CONNECT_TIMEOUT = 30
14
+ OK_CODE = 200
15
+ KEEP_ALIVE_RESPONSE = "c\r\n:keepalive\n\n\r\n".freeze
16
+ ERROR_EVENT_TYPE = 'error'.freeze
17
+
18
+ def initialize(config,
19
+ api_key,
20
+ telemetry_runtime_producer,
21
+ event_parser,
22
+ notification_manager_keeper,
23
+ notification_processor,
24
+ status_queue,
25
+ read_timeout: DEFAULT_READ_TIMEOUT)
26
+ @config = config
27
+ @api_key = api_key
28
+ @telemetry_runtime_producer = telemetry_runtime_producer
29
+ @event_parser = event_parser
30
+ @notification_manager_keeper = notification_manager_keeper
31
+ @notification_processor = notification_processor
32
+ @status_queue = status_queue
33
+ @read_timeout = read_timeout
34
+ @connected = Concurrent::AtomicBoolean.new(false)
35
+ @first_event = Concurrent::AtomicBoolean.new(true)
36
+ @socket = nil
37
+ end
38
+
39
+ def close(status = nil)
40
+ unless connected?
41
+ @config.logger.debug('SSEClient already disconected.') if @config.debug_enabled
42
+ return
43
+ end
44
+ @config.logger.debug("Closing SSEClient socket") if @config.debug_enabled
45
+
46
+ push_status(status)
47
+ @connected.make_false
48
+ @socket.sync_close = true if @socket.is_a? OpenSSL::SSL::SSLSocket
49
+ @socket.close
50
+ @config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a?(OpenSSL::SSL::SSLSocket) && @config.debug_enabled
51
+ rescue StandardError => e
52
+ @config.logger.error("SSEClient close Error: #{e.inspect}")
53
+ end
54
+
55
+ def start(url)
56
+ if connected?
57
+ @config.logger.debug('SSEClient already running.') if @config.debug_enabled
58
+ return true
59
+ end
60
+
61
+ @uri = URI(url)
62
+ latch = Concurrent::CountDownLatch.new(1)
63
+ connect_thread(latch)
64
+
65
+ return false unless latch.wait(CONNECT_TIMEOUT)
66
+
67
+ connected?
68
+ rescue StandardError => e
69
+ @config.logger.error("SSEClient start Error: #{e.inspect}")
70
+ connected?
71
+ end
72
+
73
+ def connected?
74
+ @connected.value
75
+ end
76
+
77
+ private
78
+
79
+ def connect_thread(latch)
80
+ @config.threads[:connect_stream] = Thread.new do
81
+ @config.logger.info('Starting connect_stream thread ...')
82
+ new_status = connect_stream(latch)
83
+ push_status(new_status)
84
+ @config.logger.info('connect_stream thread finished.')
85
+ end
86
+ end
87
+
88
+ def connect_stream(latch)
89
+ return Constants::PUSH_RETRYABLE_ERROR unless socket_write(latch)
90
+ while connected? || @first_event.value
91
+ begin
92
+ if IO.select([@socket], nil, nil, @read_timeout)
93
+ begin
94
+ partial_data = @socket.readpartial(10_000)
95
+ read_first_event(partial_data, latch)
96
+
97
+ if partial_data == :eof
98
+ @config.logger.error("SSE recived EOF unexpectedly")
99
+ return Constants::PUSH_RETRYABLE_ERROR
100
+ end
101
+ rescue IO::WaitReadable => e
102
+ @config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") if @config.debug_enabled
103
+ IO.select([@socket], nil, nil, @read_timeout)
104
+ retry
105
+ rescue Errno::EAGAIN => e
106
+ @config.logger.debug("SSE client transient error: #{e.inspect}") if @config.debug_enabled
107
+ IO.select([@socket], nil, nil, @read_timeout)
108
+ retry
109
+ rescue Errno::ETIMEDOUT => e
110
+ @config.logger.error("SSE read operation timed out!: #{e.inspect}")
111
+ return Constants::PUSH_RETRYABLE_ERROR
112
+ rescue EOFError => e
113
+ @config.logger.error("SSE read operation EOF Exception!: #{e.inspect}")
114
+ return Constants::PUSH_RETRYABLE_ERROR
115
+ rescue Errno::EBADF, IOError => e
116
+ @config.logger.error("SSE read operation EBADF or IOError: #{e.inspect}")
117
+ return Constants::PUSH_RETRYABLE_ERROR
118
+ rescue StandardError => e
119
+ @config.logger.error("SSE read operation StandardError: #{e.inspect}")
120
+ return nil if ENV['SPLITCLIENT_ENV'] == 'test'
121
+
122
+ @config.logger.error("Error reading partial data: #{e.inspect}")
123
+ return Constants::PUSH_RETRYABLE_ERROR
124
+ end
125
+ else
126
+ @config.logger.error("SSE read operation timed out, no data available.")
127
+ return Constants::PUSH_RETRYABLE_ERROR
128
+ end
129
+ rescue Errno::EBADF
130
+ @config.logger.debug("SSE socket is not connected (Errno::EBADF)") if @config.debug_enabled
131
+ break
132
+ rescue Exception => e
133
+ @config.logger.debug("SSE socket is not connected: #{e.inspect}") if @config.debug_enabled
134
+ break
135
+ end
136
+
137
+ process_data(partial_data)
138
+ end
139
+ @config.logger.info("SSE read operation exited: #{connected?}")
140
+
141
+ nil
142
+ end
143
+
144
+ def socket_write(latch)
145
+ @first_event.make_true
146
+ @socket = socket_connect
147
+ @socket.puts(build_request(@uri))
148
+ true
149
+ rescue StandardError => e
150
+ @config.logger.error("Error during connecting to #{@uri.host}. Error: #{e.inspect}")
151
+ latch.count_down
152
+ false
153
+ end
154
+
155
+ def read_first_event(data, latch)
156
+ return unless @first_event.value
157
+
158
+ response_code = @event_parser.first_event(data)
159
+ @config.logger.debug("SSE client first event code: #{response_code}") if @config.debug_enabled
160
+
161
+ error_event = false
162
+ events = @event_parser.parse(data)
163
+ events.each { |e| error_event = true if e.event_type == ERROR_EVENT_TYPE }
164
+ @first_event.make_false
165
+
166
+ if response_code == OK_CODE && !error_event
167
+ @connected.make_true
168
+ @config.logger.debug("SSE client first event Connected is true") if @config.debug_enabled
169
+ @telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::SSE_CONNECTION_ESTABLISHED, nil)
170
+ push_status(Constants::PUSH_CONNECTED)
171
+ end
172
+
173
+ latch.count_down
174
+ end
175
+
176
+ def socket_connect
177
+ tcp_socket = TCPSocket.new(@uri.host, @uri.port)
178
+ if @uri.scheme.casecmp('https').zero?
179
+ begin
180
+ ssl_context = OpenSSL::SSL::SSLContext.new
181
+ ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
182
+ ssl_socket.hostname = @uri.host
183
+
184
+ begin
185
+ ssl_socket.connect_nonblock
186
+ rescue IO::WaitReadable
187
+ IO.select([ssl_socket])
188
+ retry
189
+ rescue IO::WaitWritable
190
+ IO.select(nil, [ssl_socket])
191
+ retry
192
+ end
193
+ return ssl_socket
194
+
195
+ rescue Exception => e
196
+ @config.logger.error("socket connect error: #{e.inspect}")
197
+ return nil
198
+ end
199
+ end
200
+
201
+ tcp_socket
202
+ end
203
+
204
+ def process_data(partial_data)
205
+ @config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
206
+ return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE
207
+
208
+ events = @event_parser.parse(partial_data)
209
+ events.each { |event| process_event(event) }
210
+ rescue StandardError => e
211
+ @config.logger.error("process_data error: #{e.inspect}")
212
+ end
213
+
214
+ def build_request(uri)
215
+ req = "GET #{uri.request_uri} HTTP/1.1\r\n"
216
+ req << "Host: #{uri.host}\r\n"
217
+ req << "Accept: text/event-stream\r\n"
218
+ req << "SplitSDKVersion: #{@config.language}-#{@config.version}\r\n"
219
+ req << "SplitSDKMachineIP: #{@config.machine_ip}\r\n"
220
+ req << "SplitSDKMachineName: #{@config.machine_name}\r\n"
221
+ req << "SplitSDKClientKey: #{@api_key.split(//).last(4).join}\r\n" unless @api_key.nil?
222
+ req << "Cache-Control: no-cache\r\n\r\n"
223
+ @config.logger.debug("Request info: #{req}") if @config.debug_enabled
224
+ req
225
+ end
226
+
227
+ def process_event(event)
228
+ case event.event_type
229
+ when ERROR_EVENT_TYPE
230
+ dispatch_error(event)
231
+ else
232
+ dispatch_event(event)
233
+ end
234
+ end
235
+
236
+ def dispatch_error(event)
237
+ @config.logger.error("Event error: #{event.event_type}, #{event.data}")
238
+ @telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::ABLY_ERROR, event.data['code'])
239
+
240
+ if event.data['code'] >= 40_140 && event.data['code'] <= 40_149
241
+ close(Constants::PUSH_RETRYABLE_ERROR)
242
+ elsif event.data['code'] >= 40_000 && event.data['code'] <= 49_999
243
+ close(Constants::PUSH_NONRETRYABLE_ERROR)
244
+ end
245
+ end
246
+
247
+ def dispatch_event(event)
248
+ if event.occupancy?
249
+ @notification_manager_keeper.handle_incoming_occupancy_event(event)
250
+ else
251
+ @notification_processor.process(event)
252
+ end
253
+ end
254
+
255
+ def push_status(status)
256
+ return if status.nil?
257
+
258
+ @config.logger.debug("Pushing new sse status: #{status}") if @config.debug_enabled
259
+ @status_queue.push(status)
260
+ end
261
+ end
262
+ end
263
+ end
264
+ end