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,231 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ class Synchronizer
6
+ include SplitIoClient::Cache::Fetchers
7
+ include SplitIoClient::Cache::Senders
8
+
9
+ ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES = 10
10
+
11
+ def initialize(
12
+ repositories,
13
+ config,
14
+ params
15
+ )
16
+ @splits_repository = repositories[:splits]
17
+ @segments_repository = repositories[:segments]
18
+ @rule_based_segments_repository = repositories[:rule_based_segments]
19
+ @impressions_repository = repositories[:impressions]
20
+ @events_repository = repositories[:events]
21
+ @config = config
22
+ @split_fetcher = params[:split_fetcher]
23
+ @segment_fetcher = params[:segment_fetcher]
24
+ @impressions_api = params[:impressions_api]
25
+ @impression_counter = params[:imp_counter]
26
+ @telemetry_synchronizer = params[:telemetry_synchronizer]
27
+ @impressions_sender_adapter = params[:impressions_sender_adapter]
28
+ @unique_keys_tracker = params[:unique_keys_tracker]
29
+
30
+ @splits_sync_backoff = Engine::BackOff.new(10, 0, 60)
31
+ @segments_sync_backoff = Engine::BackOff.new(10, 0, 60)
32
+ end
33
+
34
+ def sync_all(asynchronous = true)
35
+ unless asynchronous
36
+ return sync_splits_and_segments
37
+ end
38
+
39
+ @config.threads[:sync_all_thread] = Thread.new do
40
+ sync_splits_and_segments
41
+ end
42
+
43
+ true
44
+ end
45
+
46
+ def start_periodic_data_recording
47
+ unless @config.consumer?
48
+ impressions_sender
49
+ events_sender
50
+ start_telemetry_sync_task
51
+ end
52
+
53
+ impressions_count_sender
54
+ start_unique_keys_tracker_task
55
+ end
56
+
57
+ def start_periodic_fetch
58
+ @split_fetcher.call
59
+ @segment_fetcher.call
60
+ end
61
+
62
+ def stop_periodic_fetch
63
+ @split_fetcher.stop_splits_thread
64
+ @segment_fetcher.stop_segments_thread
65
+ end
66
+
67
+ def fetch_splits(target_change_number, rbs_target_change_number)
68
+ return if check_exit_conditions(target_change_number, rbs_target_change_number)
69
+
70
+ fetch_options = { cache_control_headers: true, till: nil }
71
+
72
+ result = attempt_splits_sync(target_change_number, rbs_target_change_number,
73
+ fetch_options,
74
+ @config.on_demand_fetch_max_retries,
75
+ @config.on_demand_fetch_retry_delay_seconds,
76
+ false)
77
+
78
+ attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
79
+ if result[:success]
80
+ @segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty?
81
+ @config.logger.debug("Refresh completed in #{attempts} attempts.") if @config.debug_enabled
82
+
83
+ return
84
+ end
85
+
86
+ if target_change_number != 0
87
+ fetch_options[:till] = target_change_number
88
+ else
89
+ fetch_options[:till] = rbs_target_change_number
90
+ end
91
+
92
+ result = attempt_splits_sync(target_change_number, rbs_target_change_number,
93
+ fetch_options,
94
+ ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES,
95
+ nil,
96
+ true)
97
+
98
+ attempts = ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES - result[:remaining_attempts]
99
+
100
+ process_result(result, attempts)
101
+ rescue StandardError => e
102
+ @config.log_found_exception(__method__.to_s, e)
103
+ end
104
+
105
+ def fetch_segment(name, target_change_number)
106
+ return if target_change_number <= @segments_repository.get_change_number(name).to_i
107
+
108
+ fetch_options = { cache_control_headers: true, till: nil }
109
+ result = attempt_segment_sync(name,
110
+ target_change_number,
111
+ fetch_options,
112
+ @config.on_demand_fetch_max_retries,
113
+ @config.on_demand_fetch_retry_delay_seconds,
114
+ false)
115
+
116
+ attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
117
+ if result[:success]
118
+ @config.logger.debug("Segment #{name} refresh completed in #{attempts} attempts.") if @config.debug_enabled
119
+
120
+ return
121
+ end
122
+
123
+ fetch_options = { cache_control_headers: true, till: target_change_number }
124
+ result = attempt_segment_sync(name,
125
+ target_change_number,
126
+ fetch_options,
127
+ ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES,
128
+ nil,
129
+ true)
130
+
131
+ attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
132
+ if result[:success]
133
+ @config.logger.debug("Segment #{name} refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled
134
+ else
135
+ @config.logger.debug("No changes fetched for segment #{name} after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled
136
+ end
137
+ rescue StandardError => e
138
+ @config.log_found_exception(__method__.to_s, e)
139
+ end
140
+
141
+ private
142
+
143
+ def process_result(result, attempts)
144
+ if result[:success]
145
+ @segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty?
146
+ @config.logger.debug("Refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled
147
+ else
148
+ @config.logger.debug("No changes fetched after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled
149
+ end
150
+ end
151
+
152
+ def attempt_segment_sync(name, target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff)
153
+ remaining_attempts = max_retries
154
+ @segments_sync_backoff.reset
155
+
156
+ loop do
157
+ remaining_attempts -= 1
158
+
159
+ @segment_fetcher.fetch_segment(name, fetch_options)
160
+
161
+ return sync_result(true, remaining_attempts) if target_cn <= @segments_repository.get_change_number(name).to_i
162
+ return sync_result(false, remaining_attempts) if remaining_attempts <= 0
163
+
164
+ delay = with_backoff ? @segments_sync_backoff.interval : retry_delay_seconds
165
+ sleep(delay)
166
+ end
167
+ end
168
+
169
+ def attempt_splits_sync(target_cn, rbs_target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff)
170
+ remaining_attempts = max_retries
171
+ @splits_sync_backoff.reset
172
+
173
+ loop do
174
+ remaining_attempts -= 1
175
+
176
+ result = @split_fetcher.fetch_splits(fetch_options)
177
+
178
+ return sync_result(true, remaining_attempts, result[:segment_names]) if check_exit_conditions(target_cn, rbs_target_cn)
179
+ return sync_result(false, remaining_attempts, result[:segment_names]) if remaining_attempts <= 0
180
+
181
+ delay = with_backoff ? @splits_sync_backoff.interval : retry_delay_seconds
182
+ sleep(delay)
183
+ end
184
+ end
185
+
186
+ # Starts thread which loops constantly and sends impressions to the Split API
187
+ def impressions_sender
188
+ ImpressionsSender.new(@impressions_repository, @config, @impressions_api).call
189
+ end
190
+
191
+ # Starts thread which loops constantly and sends events to the Split API
192
+ def events_sender
193
+ EventsSender.new(@events_repository, @config).call
194
+ end
195
+
196
+ # Starts thread which loops constantly and sends impressions count to the Split API
197
+ def impressions_count_sender
198
+ ImpressionsCountSender.new(@config, @impression_counter, @impressions_sender_adapter).call
199
+ end
200
+
201
+ def start_telemetry_sync_task
202
+ Telemetry::SyncTask.new(@config, @telemetry_synchronizer).call
203
+ end
204
+
205
+ def start_unique_keys_tracker_task
206
+ @unique_keys_tracker.call
207
+ end
208
+
209
+ def sync_result(success, remaining_attempts, segment_names = nil)
210
+ { success: success, remaining_attempts: remaining_attempts, segment_names: segment_names }
211
+ end
212
+
213
+ def sync_splits_and_segments
214
+ @config.logger.debug('Synchronizing feature flags and segments ...') if @config.debug_enabled
215
+ splits_result = @split_fetcher.fetch_splits
216
+
217
+ splits_result[:success] && @segment_fetcher.fetch_segments
218
+ end
219
+
220
+ def check_exit_conditions(target_change_number, rbs_target_change_number)
221
+ return true if rbs_target_change_number == 0 and target_change_number == 0
222
+
223
+ return target_change_number <= @splits_repository.get_change_number.to_i if rbs_target_change_number == 0
224
+
225
+ return rbs_target_change_number <= @rule_based_segments_repository.get_change_number.to_i if target_change_number == 0
226
+
227
+ return (target_change_number <= @splits_repository.get_change_number.to_i and rbs_target_change_number <= @rule_based_segments_repository.get_change_number.to_i)
228
+ end
229
+ end
230
+ end
231
+ end
@@ -0,0 +1,26 @@
1
+ module SplitIoClient
2
+ class SplitIoError < StandardError; end
3
+
4
+ class SDKShutdownException < Exception; end
5
+
6
+ class SDKBlockerTimeoutExpiredException < SplitIoError; end
7
+
8
+ class SSEClientException < SplitIoError
9
+ attr_reader :event
10
+
11
+ def initialize(event)
12
+ @event = event
13
+ end
14
+ end
15
+
16
+ class ApiException < SplitIoError
17
+ def initialize(msg, exception_code)
18
+ @@exception_code = exception_code
19
+ super(msg)
20
+ end
21
+ def exception_code
22
+ @@exception_code
23
+ end
24
+ end
25
+
26
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ NO_COMPRESSION = 0
5
+ GZIP_COMPRESSION = 1
6
+ ZLIB_COMPRESSION = 2
7
+
8
+ module Helpers
9
+ class DecryptionHelper
10
+ def self.get_encoded_definition(compression, data)
11
+ case compression
12
+ when NO_COMPRESSION
13
+ Base64.decode64(data)
14
+ when GZIP_COMPRESSION
15
+ gz = Zlib::GzipReader.new(StringIO.new(Base64.decode64(data)))
16
+ gz.read
17
+ when ZLIB_COMPRESSION
18
+ Zlib::Inflate.inflate(Base64.decode64(data))
19
+ else
20
+ raise StandardError, 'Compression flag value is incorrect'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Helpers
5
+ class EvaluatorHelper
6
+ def self.matcher_type(condition, segments_repository, rb_segment_repository)
7
+ matchers = []
8
+ segments_repository.adapter.pipelined do
9
+ condition.matchers.each do |matcher|
10
+ matchers << if matcher[:negate]
11
+ condition.negation_matcher(matcher_instance(matcher[:matcherType], condition,
12
+ matcher, segments_repository,
13
+ rb_segment_repository))
14
+ else
15
+ matcher_instance(matcher[:matcherType], condition, matcher, segments_repository, rb_segment_repository)
16
+ end
17
+ end
18
+ end
19
+ final_matcher = condition.create_condition_matcher(matchers)
20
+
21
+ if final_matcher.nil?
22
+ config.logger.error('Invalid matcher type')
23
+ else
24
+ final_matcher
25
+ end
26
+ final_matcher
27
+ end
28
+
29
+ def self.matcher_instance(type, condition, matcher, segments_repository, rb_segment_repository)
30
+ condition.send(
31
+ "matcher_#{type.downcase}",
32
+ matcher: matcher, segments_repository: segments_repository, rule_based_segments_repository: rb_segment_repository
33
+ )
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Helpers
5
+ class RepositoryHelper
6
+ def self.update_feature_flag_repository(feature_flag_repository, feature_flags, change_number, config, clear_storage)
7
+ to_add = []
8
+ to_delete = []
9
+ feature_flags.each do |feature_flag|
10
+ if Engine::Models::Split.archived?(feature_flag) || !feature_flag_repository.flag_set_filter.intersect?(feature_flag[:sets])
11
+ config.logger.debug("removing feature flag from store(#{feature_flag})") if config.debug_enabled
12
+ to_delete.push(feature_flag)
13
+ next
14
+ end
15
+
16
+ feature_flag = check_missing_elements(feature_flag, config)
17
+
18
+ config.logger.debug("storing feature flag (#{feature_flag[:name]})") if config.debug_enabled
19
+ to_add.push(feature_flag)
20
+ end
21
+ feature_flag_repository.clear if clear_storage
22
+ feature_flag_repository.update(to_add, to_delete, change_number)
23
+ end
24
+
25
+ def self.check_missing_elements(feature_flag, config)
26
+ unless feature_flag.key?(:impressionsDisabled)
27
+ feature_flag[:impressionsDisabled] = false
28
+ if config.debug_enabled
29
+ config.logger.debug("feature flag (#{feature_flag[:name]}) does not have impressionsDisabled field, setting it to false")
30
+ end
31
+ end
32
+
33
+ unless feature_flag.key?(:prerequisites)
34
+ feature_flag[:prerequisites] = []
35
+ if config.debug_enabled
36
+ config.logger.debug("feature flag (#{feature_flag[:name]}) does not have prerequisites field, setting it to empty array")
37
+ end
38
+ end
39
+
40
+ feature_flag
41
+ end
42
+
43
+ def self.update_rule_based_segment_repository(rule_based_segment_repository, rule_based_segments, change_number, config)
44
+ to_add = []
45
+ to_delete = []
46
+ rule_based_segments.each do |rule_based_segment|
47
+ if Engine::Models::Split.archived?(rule_based_segment)
48
+ config.logger.debug("removing rule based segment from store(#{rule_based_segment})") if config.debug_enabled
49
+ to_delete.push(rule_based_segment)
50
+ next
51
+ end
52
+
53
+ config.logger.debug("storing rule based segment (#{rule_based_segment[:name]})") if config.debug_enabled
54
+ to_add.push(rule_based_segment)
55
+ end
56
+
57
+ rule_based_segment_repository.update(to_add, to_delete, change_number)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Helpers
5
+ class ThreadHelper
6
+ def self.stop(thread_sym, config)
7
+ thread = config.threads[thread_sym]
8
+
9
+ unless thread.nil?
10
+ config.logger.debug("Stopping #{thread_sym} thread...") if config.debug_enabled
11
+ Thread.kill(thread)
12
+ end
13
+ rescue StandardError => e
14
+ config.logger.error(e.inspect)
15
+ end
16
+
17
+ def self.alive?(thread_sym, config)
18
+ thread = config.threads[thread_sym]
19
+
20
+ thread.nil? ? false : thread.alive?
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Helpers
5
+ class Util
6
+ def self.segment_names_by_object(object, matcher_type)
7
+ object[:conditions].each_with_object(Set.new) do |condition, names|
8
+ condition[:matcherGroup][:matchers].each do |matcher|
9
+ next if matcher[:userDefinedSegmentMatcherData].nil? || matcher[:matcherType] != matcher_type
10
+
11
+ names << matcher[:userDefinedSegmentMatcherData][:segmentName]
12
+ end
13
+ end
14
+ end
15
+
16
+ def self.segment_names_in_rb_segment(object, matcher_type)
17
+ names = Set.new
18
+ names.merge segment_names_by_object(object, matcher_type)
19
+ object[:excluded][:segments].each do |segment|
20
+ names.add(segment[:name]) if segment[:type] == SplitIoClient::Engine::Models::SegmentType::STANDARD
21
+ end
22
+ names
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,121 @@
1
+ module SplitIoClient
2
+ class SplitManager
3
+ #
4
+ # Creates a new split manager instance that connects to split.io API.
5
+ #
6
+ # @return [SplitIoManager] split.io client instance
7
+ def initialize(splits_repository = nil, status_manager, config)
8
+ @splits_repository = splits_repository
9
+ @status_manager = status_manager
10
+ @config = config
11
+ end
12
+
13
+ #
14
+ # method to get the split list from the client
15
+ #
16
+ # @returns [object] array of splits
17
+ def splits
18
+ return [] if !@config.valid_mode || @splits_repository.nil?
19
+
20
+ if !ready?
21
+ @config.logger.error("splits: the SDK is not ready, the operation cannot be executed")
22
+ return []
23
+ end
24
+
25
+ @splits_repository.splits.each_with_object([]) do |(name, split), memo|
26
+ split_view = build_split_view(name, split)
27
+
28
+ next if split_view[:name] == nil
29
+
30
+ memo << split_view unless Engine::Models::Split.archived?(split)
31
+ end
32
+ end
33
+
34
+ #
35
+ # method to get the list of just split names. Ideal for ietrating and calling client.get_treatment
36
+ #
37
+ # @returns [object] array of split names (String)
38
+ def split_names
39
+ return [] if !@config.valid_mode || @splits_repository.nil?
40
+
41
+ if !ready?
42
+ @config.logger.error("split_names: the SDK is not ready, the operation cannot be executed")
43
+ return []
44
+ end
45
+
46
+ @splits_repository.split_names
47
+ end
48
+
49
+ #
50
+ # method to get a split view
51
+ #
52
+ # @returns a split view
53
+ def split(feature_flag_name)
54
+ return unless @config.valid_mode && @splits_repository && @config.split_validator.valid_split_parameters(feature_flag_name)
55
+
56
+ if !ready?
57
+ @config.logger.error("split: the SDK is not ready, the operation cannot be executed")
58
+ return
59
+ end
60
+
61
+ sanitized_feature_flag_name = feature_flag_name.to_s.strip
62
+
63
+ if feature_flag_name.to_s != sanitized_feature_flag_name
64
+ @config.logger.warn("split: feature_flag_name #{feature_flag_name} has extra whitespace, trimming")
65
+ feature_flag_name = sanitized_feature_flag_name
66
+ end
67
+
68
+ split = @splits_repository.get_split(feature_flag_name)
69
+
70
+ if ready? && split.nil?
71
+ @config.logger.warn("split: you passed #{feature_flag_name} " \
72
+ 'that does not exist in this environment, please double check what feature flags exist in the Split user interface')
73
+ end
74
+
75
+ return if split.nil? || Engine::Models::Split.archived?(split)
76
+
77
+ build_split_view(feature_flag_name, split)
78
+ end
79
+
80
+ def block_until_ready(time = nil)
81
+ @status_manager.wait_until_ready(time) if @status_manager
82
+ end
83
+
84
+ private
85
+
86
+ def build_split_view(name, split)
87
+ return {} unless split
88
+
89
+ begin
90
+ if @config.localhost_mode
91
+ treatments = split[:conditions]
92
+ .first[:partitions]
93
+ .map { |partition| partition[:treatment] }
94
+ else
95
+ treatments = split[:conditions]
96
+ .detect { |c| c[:conditionType] == 'ROLLOUT' }[:partitions]
97
+ .map { |partition| partition[:treatment] }
98
+ end
99
+ rescue StandardError
100
+ treatments = []
101
+ end
102
+ {
103
+ name: name,
104
+ traffic_type_name: split[:trafficTypeName],
105
+ killed: split[:killed],
106
+ treatments: treatments,
107
+ change_number: split[:changeNumber],
108
+ configs: split[:configurations] || {},
109
+ sets: split[:sets] || [],
110
+ default_treatment: split[:defaultTreatment],
111
+ impressions_disabled: split[:impressionsDisabled]
112
+ }
113
+ end
114
+
115
+ # move to blocker, alongside block until ready to avoid duplication
116
+ def ready?
117
+ return @status_manager.ready? if @status_manager
118
+ true
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Spec
5
+ class FeatureFlags
6
+ SPEC_VERSION = "1.3"
7
+ end
8
+ end
9
+ end