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,53 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Adapters
4
+ module MemoryAdapters
5
+ # Memory adapter implementation, which stores everything inside queue
6
+ class QueueAdapter
7
+ def initialize(max_size)
8
+ @max_size = max_size
9
+ @queue = Queue.new
10
+ @current_size = Concurrent::AtomicFixnum.new(0)
11
+ end
12
+
13
+ def clear(_ = nil)
14
+ @queue = Queue.new
15
+ @current_size.value = 0
16
+ end
17
+
18
+ # Adds data to queue in non-blocking mode
19
+ def add_to_queue(data)
20
+ fail ThreadError if @current_size.value >= @max_size
21
+
22
+ @queue.push(data)
23
+
24
+ @current_size.increment
25
+ end
26
+
27
+ def clear
28
+ get_batch(@current_size.value)
29
+ end
30
+
31
+ def get_batch(size)
32
+ items = []
33
+ size.times do
34
+ items << @queue.pop(true)
35
+ @current_size.decrement
36
+ end
37
+ items
38
+ rescue ThreadError
39
+ items
40
+ end
41
+
42
+ def length
43
+ @current_size.value
44
+ end
45
+
46
+ def empty?
47
+ @queue.empty?
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module SplitIoClient
6
+ module Cache
7
+ module Adapters
8
+ # Redis adapter used to provide interface to Redis
9
+ class RedisAdapter
10
+ SCAN_SLICE = 5000
11
+
12
+ attr_reader :redis
13
+
14
+ def initialize(redis_url)
15
+ connection = redis_url.is_a?(Hash) ? redis_url : { url: redis_url }
16
+
17
+ @redis = Redis.new(connection)
18
+ end
19
+
20
+ def hincrby(key, field, increment)
21
+ @redis.hincrby(key, field, increment)
22
+ end
23
+
24
+ # Map
25
+ def initialize_map(key)
26
+ # No need to initialize hash/map in Redis
27
+ end
28
+
29
+ def add_to_map(key, field, value)
30
+ @redis.hset(key, field, value)
31
+ end
32
+
33
+ def find_in_map(key, field)
34
+ @redis.hget(key, field)
35
+ end
36
+
37
+ def delete_from_map(key, field)
38
+ @redis.hdel(key, field)
39
+ end
40
+
41
+ def in_map?(key, field)
42
+ @redis.hexists(key, field)
43
+ end
44
+
45
+ def map_keys(key)
46
+ @redis.hkeys(key)
47
+ end
48
+
49
+ def get_map(key)
50
+ @redis.hgetall(key)
51
+ end
52
+
53
+ # String
54
+ def string(key)
55
+ @redis.get(key)
56
+ end
57
+
58
+ def set_string(key, str)
59
+ @redis.set(key, str)
60
+ end
61
+
62
+ def find_strings_by_prefix(prefix)
63
+ find_strings_by_pattern("#{prefix}*")
64
+ end
65
+
66
+ def find_strings_by_pattern(pattern)
67
+ memo = { items: [], cursor: 0 }
68
+
69
+ loop do
70
+ memo[:cursor], items = @redis.scan(memo[:cursor], match: "#{pattern}", count: SCAN_SLICE)
71
+
72
+ memo[:items].push(*items)
73
+
74
+ break if memo[:cursor] == '0'
75
+ end
76
+
77
+ memo[:items]
78
+ end
79
+
80
+ def multiple_strings(keys)
81
+ Hash[keys.zip(@redis.mget(keys))]
82
+ end
83
+
84
+ def append_to_string(key, val)
85
+ @redis.append(key, val)
86
+ end
87
+
88
+ # Bool
89
+ def set_bool(key, val)
90
+ @redis.set(key, val.to_s)
91
+ end
92
+
93
+ def bool(key)
94
+ @redis.get(key) == 'true'
95
+ end
96
+
97
+ # Set
98
+ alias initialize_set initialize_map
99
+ alias find_sets_by_prefix find_strings_by_prefix
100
+
101
+ def add_to_set(key, val)
102
+ @redis.sadd?(key, val)
103
+ end
104
+
105
+ def delete_from_set(key, val)
106
+ @redis.srem(key, val)
107
+ end
108
+
109
+ def get_set(key)
110
+ @redis.smembers(key)
111
+ end
112
+
113
+ def in_set?(key, val)
114
+ @redis.sismember(key, val)
115
+ end
116
+
117
+ def get_all_from_set(key)
118
+ @redis.smembers(key)
119
+ end
120
+
121
+ def union_sets(set_keys)
122
+ return [] if set_keys == []
123
+
124
+ @redis.sunion(set_keys)
125
+ end
126
+
127
+ def random_set_elements(key, count)
128
+ @redis.srandmember(key, count)
129
+ end
130
+
131
+ # Queue
132
+ def add_to_queue(key, val)
133
+ @redis.rpush(key, val)
134
+ end
135
+
136
+ # count = 0 will result in lrange(0,-1), fetching all items
137
+ def get_from_queue(key, count)
138
+ items = @redis.lrange(key, 0, count - 1)
139
+ fetched_count = items.size
140
+ items_to_remove = fetched_count == count ? count : fetched_count
141
+
142
+ @redis.ltrim(key, items_to_remove, -1)
143
+
144
+ items
145
+ end
146
+
147
+ # General
148
+ def exists?(key)
149
+ @redis.exists?(key)
150
+ end
151
+
152
+ def delete(key)
153
+ return nil if key == []
154
+
155
+ @redis.del(key)
156
+ end
157
+
158
+ def inc(key, inc = 1)
159
+ @redis.incrby(key, inc)
160
+ end
161
+
162
+ def pipelined
163
+ @redis.pipelined do |pipeline|
164
+ yield
165
+ end
166
+ end
167
+
168
+ def clear(prefix)
169
+ # noop
170
+ end
171
+
172
+ def expire(key, seconds)
173
+ @redis.expire(key, seconds)
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,83 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Fetchers
4
+ class SegmentFetcher
5
+ attr_reader :segments_repository
6
+
7
+ def initialize(segments_repository, api_key, config, telemetry_runtime_producer)
8
+ @segments_repository = segments_repository
9
+ @api_key = api_key
10
+ @config = config
11
+ @semaphore = Mutex.new
12
+ @telemetry_runtime_producer = telemetry_runtime_producer
13
+ end
14
+
15
+ def call
16
+ if ENV['SPLITCLIENT_ENV'] == 'test'
17
+ fetch_segments
18
+ return
19
+ end
20
+
21
+ segments_thread
22
+ end
23
+
24
+ def fetch_segments_if_not_exists(names, cache_control_headers = false)
25
+ names.each do |name|
26
+ change_number = @segments_repository.get_change_number(name)
27
+
28
+ if change_number == -1
29
+ fetch_options = { cache_control_headers: cache_control_headers, till: nil }
30
+ fetch_segment(name, fetch_options) if change_number == -1
31
+ end
32
+ end
33
+ rescue StandardError => e
34
+ @config.log_found_exception(__method__.to_s, e)
35
+ end
36
+
37
+ def fetch_segment(name, fetch_options = { cache_control_headers: false, till: nil })
38
+ @semaphore.synchronize do
39
+ segments_api.fetch_segments_by_names([name], fetch_options)
40
+ end
41
+ rescue StandardError => e
42
+ @config.log_found_exception(__method__.to_s, e)
43
+ end
44
+
45
+ def fetch_segments
46
+ @semaphore.synchronize do
47
+ segments_api.fetch_segments_by_names(@segments_repository.used_segment_names)
48
+
49
+ true
50
+ end
51
+ rescue StandardError => e
52
+ @config.log_found_exception(__method__.to_s, e)
53
+ false
54
+ end
55
+
56
+ def stop_segments_thread
57
+ SplitIoClient::Helpers::ThreadHelper.stop(:segment_fetcher, @config)
58
+ end
59
+
60
+ private
61
+
62
+ def segments_thread
63
+ @config.threads[:segment_fetcher] = Thread.new do
64
+ @config.logger.info('Starting segments fetcher service') if @config.debug_enabled
65
+
66
+ loop do
67
+ fetch_segments
68
+ @config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
69
+
70
+ sleep_for = SplitIoClient::Cache::Stores::StoreUtils.random_interval(@config.segments_refresh_rate)
71
+ @config.logger.debug("Segments fetcher is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
72
+ sleep(sleep_for)
73
+ end
74
+ end
75
+ end
76
+
77
+ def segments_api
78
+ @segments_api ||= SplitIoClient::Api::Segments.new(@api_key, @segments_repository, @config, @telemetry_runtime_producer)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,70 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Fetchers
4
+ class SplitFetcher
5
+ attr_reader :splits_repository, :rule_based_segments_repository
6
+
7
+ def initialize(splits_repository, rule_based_segments_repository, api_key, config, telemetry_runtime_producer)
8
+ @splits_repository = splits_repository
9
+ @rule_based_segments_repository = rule_based_segments_repository
10
+ @api_key = api_key
11
+ @config = config
12
+ @semaphore = Mutex.new
13
+ @telemetry_runtime_producer = telemetry_runtime_producer
14
+ end
15
+
16
+ def call
17
+ if ENV['SPLITCLIENT_ENV'] == 'test'
18
+ fetch_splits
19
+ return
20
+ end
21
+
22
+ splits_thread
23
+ end
24
+
25
+ def fetch_splits(fetch_options = { cache_control_headers: false, till: nil })
26
+ @semaphore.synchronize do
27
+ data = splits_since(@splits_repository.get_change_number, @rule_based_segments_repository.get_change_number, fetch_options)
28
+ SplitIoClient::Helpers::RepositoryHelper.update_feature_flag_repository(@splits_repository, data[:ff][:d], data[:ff][:t], @config, @splits_api.clear_storage)
29
+ SplitIoClient::Helpers::RepositoryHelper.update_rule_based_segment_repository(@rule_based_segments_repository, data[:rbs][:d], data[:rbs][:t], @config)
30
+ @splits_repository.set_segment_names(data[:segment_names])
31
+ @rule_based_segments_repository.set_segment_names(data[:segment_names])
32
+ @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
33
+
34
+ { segment_names: data[:segment_names], success: true }
35
+ end
36
+ rescue StandardError => e
37
+ @config.log_found_exception(__method__.to_s, e)
38
+ { segment_names: [], success: false }
39
+ end
40
+
41
+ def stop_splits_thread
42
+ SplitIoClient::Helpers::ThreadHelper.stop(:split_fetcher, @config)
43
+ end
44
+
45
+ private
46
+
47
+ def splits_thread
48
+ @config.threads[:split_fetcher] = Thread.new do
49
+ @config.logger.info('Starting feature flags fetcher service') if @config.debug_enabled
50
+ loop do
51
+ fetch_splits
52
+
53
+ sleep_for = SplitIoClient::Cache::Stores::StoreUtils.random_interval(@config.features_refresh_rate)
54
+ @config.logger.debug("Feature flags fetcher is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
55
+ sleep(sleep_for)
56
+ end
57
+ end
58
+ end
59
+
60
+ def splits_since(since, since_rbs, fetch_options = { cache_control_headers: false, till: nil })
61
+ splits_api.since(since, since_rbs, fetch_options)
62
+ end
63
+
64
+ def splits_api
65
+ @splits_api ||= SplitIoClient::Api::Splits.new(@api_key, @config, @telemetry_runtime_producer)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bitarray'
4
+
5
+ module SplitIoClient
6
+ module Cache
7
+ module Filter
8
+ class BloomFilter
9
+ def initialize(capacity, false_positive_probability = 0.001)
10
+ @capacity = capacity.round
11
+ @m = best_m(capacity, false_positive_probability)
12
+ reset_filter
13
+ @k = best_k(capacity)
14
+ end
15
+
16
+ def add(string)
17
+ return false if contains?(string)
18
+
19
+ positions = hashes(string)
20
+ positions.each { |position| @ba[position] = 1 }
21
+
22
+ true
23
+ end
24
+
25
+ def contains?(string)
26
+ !hashes(string).any? { |ea| @ba[ea] == 0 }
27
+ end
28
+
29
+ def clear
30
+ @ba = nil
31
+ reset_filter
32
+ end
33
+
34
+ private
35
+
36
+ def reset_filter
37
+ @ba = BitArray.new(@m.round)
38
+ end
39
+
40
+ # m is the required number of bits in the array
41
+ def best_m(capacity, false_positive_probability)
42
+ -(capacity * Math.log(false_positive_probability)) / (Math.log(2) ** 2)
43
+ end
44
+
45
+ # k is the number of hash functions that minimizes the probability of false positives
46
+ def best_k(capacity)
47
+ (Math.log(2) * (@ba.size / capacity)).round
48
+ end
49
+
50
+ def hashes(data)
51
+ m = @ba.size
52
+ h = Digest::MD5.hexdigest(data.to_s).to_i(16)
53
+ x = h % m
54
+ h /= m
55
+ y = h % m
56
+ h /= m
57
+ z = h % m
58
+ [x] + 1.upto(@k - 1).collect do |i|
59
+ x = (x + y) % m
60
+ y = (y + z) % m
61
+ x
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Cache
5
+ module Filter
6
+ class FilterAdapter
7
+ def initialize(config, filter)
8
+ @config = config
9
+ @filter = filter
10
+ end
11
+
12
+ def add(feature_name, key)
13
+ @filter.add("#{feature_name}#{key}")
14
+ rescue StandardError => e
15
+ @config.log_found_exception(__method__.to_s, e)
16
+ end
17
+
18
+ def contains?(feature_name, key)
19
+ @filter.contains?("#{feature_name}#{key}")
20
+ rescue StandardError => e
21
+ @config.log_found_exception(__method__.to_s, e)
22
+ end
23
+
24
+ def clear
25
+ @filter.clear
26
+ rescue StandardError => e
27
+ @config.log_found_exception(__method__.to_s, e)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'set'
4
+
5
+ module SplitIoClient
6
+ module Cache
7
+ module Filter
8
+ class FlagSetsFilter
9
+ def initialize(flag_sets = [])
10
+ @flag_sets = Set.new(flag_sets)
11
+ @should_filter = @flag_sets.any?
12
+ end
13
+
14
+ def should_filter?
15
+ @should_filter
16
+ end
17
+
18
+ def flag_set_exist?(flag_set)
19
+ return true unless @should_filter
20
+
21
+ if not flag_set.is_a?(String) or flag_set.empty?
22
+ return false
23
+ end
24
+
25
+ @flag_sets.intersection([flag_set]).any?
26
+ end
27
+
28
+ def intersect?(flag_sets)
29
+ return true unless @should_filter
30
+
31
+ if not flag_sets.is_a?(Array) or flag_sets.empty?
32
+ return false
33
+ end
34
+
35
+ @flag_sets.intersection(Set.new(flag_sets)).any?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,34 @@
1
+ module SplitIoClient
2
+ module Hashers
3
+ class ImpressionHasher
4
+ def initialize
5
+ @murmur_hash_128_64 = case RUBY_PLATFORM
6
+ when 'java'
7
+ Proc.new { |key, seed| Java::MurmurHash3.hash128x64(key, seed) }
8
+ else
9
+ Proc.new { |key, seed| Digest::MurmurHashMRI3_x64_128.rawdigest(key, [seed].pack('L')) }
10
+ end
11
+ end
12
+
13
+ def process(impression)
14
+ impression_data = "#{unknown_if_null(impression[:k])}"
15
+ impression_data << ":#{unknown_if_null(impression[:f])}"
16
+ impression_data << ":#{unknown_if_null(impression[:t])}"
17
+ impression_data << ":#{unknown_if_null(impression[:r])}"
18
+ impression_data << ":#{zero_if_null(impression[:c])}"
19
+
20
+ @murmur_hash_128_64.call(impression_data, 0)[0];
21
+ end
22
+
23
+ private
24
+
25
+ def unknown_if_null(value)
26
+ value == nil ? "UNKNOWN" : value
27
+ end
28
+
29
+ def zero_if_null(value)
30
+ value == nil ? 0 : value
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ module SplitIoClient
2
+ module Observers
3
+ class ImpressionObserver
4
+ LAST_SEEN_CACHE_SIZE = 500000
5
+
6
+ def initialize
7
+ @cache = LruRedux::TTL::ThreadSafeCache.new(LAST_SEEN_CACHE_SIZE)
8
+ @impression_hasher = Hashers::ImpressionHasher.new
9
+ end
10
+
11
+ def test_and_set(impression)
12
+ return if impression.nil?
13
+
14
+ hash = @impression_hasher.process(impression)
15
+ previous = @cache[hash]
16
+ @cache[hash] = impression[:m]
17
+
18
+ previous.nil? ? nil : [previous, impression[:m]].min
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ module SplitIoClient
2
+ module Observers
3
+ class NoopImpressionObserver
4
+ def test_and_set(impression)
5
+ # no-op
6
+ end
7
+ end
8
+ end
9
+ end
10
+
@@ -0,0 +1,45 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Events
5
+ class MemoryRepository < EventsRepository
6
+ EVENTS_MAX_SIZE_BYTES = 5242880
7
+
8
+ def initialize(config, telemetry_runtime_producer)
9
+ @config = config
10
+ @adapter = @config.events_adapter
11
+ @size = 0
12
+ @telemetry_runtime_producer = telemetry_runtime_producer
13
+ end
14
+
15
+ def add(key, traffic_type, event_type, time, value, properties, event_size)
16
+ @adapter.add_to_queue(m: metadata, e: event(key, traffic_type, event_type, time, value, properties))
17
+ @size += event_size
18
+
19
+ post_events if @size >= EVENTS_MAX_SIZE_BYTES || @adapter.length == @config.events_queue_size
20
+
21
+ @telemetry_runtime_producer.record_events_stats(Telemetry::Domain::Constants::EVENTS_QUEUED, 1)
22
+ rescue StandardError => e
23
+ @config.log_found_exception(__method__.to_s, e)
24
+ @telemetry_runtime_producer.record_events_stats(Telemetry::Domain::Constants::EVENTS_DROPPED, 1)
25
+ end
26
+
27
+ def clear
28
+ @size = 0
29
+ @adapter.clear
30
+ end
31
+
32
+ def empty?
33
+ @adapter.empty?
34
+ end
35
+
36
+ def batch
37
+ return [] if @config.events_queue_size.zero?
38
+
39
+ @adapter.get_batch(@config.events_queue_size)
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ module SplitIoClient
2
+ module Cache
3
+ module Repositories
4
+ module Events
5
+ class RedisRepository < EventsRepository
6
+
7
+ def initialize(config)
8
+ @config = config
9
+ @adapter = @config.events_adapter
10
+ end
11
+
12
+ def add(key, traffic_type, event_type, time, value, properties, size)
13
+ @adapter.add_to_queue(
14
+ namespace_key('.events'),
15
+ { m: metadata, e: event(key, traffic_type, event_type, time, value, properties) }.to_json
16
+ )
17
+ end
18
+
19
+ def clear
20
+ @adapter.get_from_queue(namespace_key('.events'), 0).map do |e|
21
+ JSON.parse(e, symbolize_names: true)
22
+ end
23
+ rescue StandardError => e
24
+ @config.logger.error("Exception while clearing events cache: #{e}")
25
+ []
26
+ end
27
+
28
+ def batch
29
+ clear()
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end