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.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +1 -0
- data/.github/pull_request_template.md +9 -0
- data/.github/workflows/ci.yml +90 -0
- data/.github/workflows/update-license-year.yml +45 -0
- data/.gitignore +20 -0
- data/.rubocop.yml +76 -0
- data/.simplecov +1 -0
- data/CHANGES.txt +361 -2
- data/CONTRIBUTORS-GUIDE.md +49 -0
- data/Gemfile +2 -0
- data/LICENSE +3 -36
- data/NOTICE.txt +5 -0
- data/README.md +59 -127
- data/Rakefile +27 -3
- data/ext/murmurhash/MurmurHash3.java +302 -0
- data/gemfiles/faraday_after_0.13.gemfile +7 -0
- data/gemfiles/faraday_before_0.13.gemfile +8 -0
- data/lib/murmurhash/base.rb +58 -0
- data/lib/murmurhash/murmurhash.jar +0 -0
- data/lib/murmurhash/murmurhash_mri.rb +3 -0
- data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +130 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapter.rb +12 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +137 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +53 -0
- data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +178 -0
- data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +83 -0
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +70 -0
- data/lib/splitclient-rb/cache/filter/bloom_filter.rb +67 -0
- data/lib/splitclient-rb/cache/filter/filter_adapter.rb +32 -0
- data/lib/splitclient-rb/cache/filter/flag_set_filter.rb +40 -0
- data/lib/splitclient-rb/cache/hashers/impression_hasher.rb +34 -0
- data/lib/splitclient-rb/cache/observers/impression_observer.rb +22 -0
- data/lib/splitclient-rb/cache/observers/noop_impression_observer.rb +10 -0
- data/lib/splitclient-rb/cache/repositories/events/memory_repository.rb +45 -0
- data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +35 -0
- data/lib/splitclient-rb/cache/repositories/events_repository.rb +61 -0
- data/lib/splitclient-rb/cache/repositories/flag_sets/memory_repository.rb +40 -0
- data/lib/splitclient-rb/cache/repositories/flag_sets/redis_repository.rb +49 -0
- data/lib/splitclient-rb/cache/repositories/impressions/memory_repository.rb +56 -0
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +57 -0
- data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +23 -0
- data/lib/splitclient-rb/cache/repositories/repository.rb +24 -0
- data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +124 -0
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +296 -0
- data/lib/splitclient-rb/cache/routers/impression_router.rb +60 -0
- data/lib/splitclient-rb/cache/senders/events_sender.rb +42 -0
- data/lib/splitclient-rb/cache/senders/impressions_adapter/memory_sender.rb +71 -0
- data/lib/splitclient-rb/cache/senders/impressions_adapter/redis_sender.rb +69 -0
- data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +43 -0
- data/lib/splitclient-rb/cache/senders/impressions_formatter.rb +95 -0
- data/lib/splitclient-rb/cache/senders/impressions_sender.rb +52 -0
- data/lib/splitclient-rb/cache/senders/impressions_sender_adapter.rb +21 -0
- data/lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb +47 -0
- data/lib/splitclient-rb/cache/stores/localhost_split_builder.rb +95 -0
- data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +110 -0
- data/lib/splitclient-rb/cache/stores/store_utils.rb +13 -0
- data/lib/splitclient-rb/clients/split_client.rb +530 -0
- data/lib/splitclient-rb/constants.rb +16 -0
- data/lib/splitclient-rb/engine/api/client.rb +87 -0
- data/lib/splitclient-rb/engine/api/events.rb +57 -0
- data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +58 -0
- data/lib/splitclient-rb/engine/api/impressions.rb +79 -0
- data/lib/splitclient-rb/engine/api/segments.rb +78 -0
- data/lib/splitclient-rb/engine/api/splits.rb +139 -0
- data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
- data/lib/splitclient-rb/engine/auth_api_client.rb +100 -0
- data/lib/splitclient-rb/engine/back_off.rb +26 -0
- data/lib/splitclient-rb/engine/common/impressions_counter.rb +45 -0
- data/lib/splitclient-rb/engine/common/impressions_manager.rb +165 -0
- data/lib/splitclient-rb/engine/common/noop_impressions_counter.rb +27 -0
- data/lib/{splitclient-engine → splitclient-rb/engine}/evaluator/splitter.rb +35 -22
- data/lib/splitclient-rb/engine/events/events_delivery.rb +20 -0
- data/lib/splitclient-rb/engine/events/events_manager.rb +194 -0
- data/lib/splitclient-rb/engine/events/events_manager_config.rb +96 -0
- data/lib/splitclient-rb/engine/events/events_task.rb +50 -0
- data/lib/splitclient-rb/engine/events/noop_events_queue.rb +13 -0
- data/lib/splitclient-rb/engine/fallback_treatment_calculator.rb +48 -0
- data/lib/splitclient-rb/engine/impressions/noop_unique_keys_tracker.rb +17 -0
- data/lib/splitclient-rb/engine/impressions/unique_keys_tracker.rb +144 -0
- data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/all_keys_matcher.rb +10 -21
- data/lib/splitclient-rb/engine/matchers/between_matcher.rb +47 -0
- data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
- data/lib/{splitclient-engine → splitclient-rb/engine}/matchers/combiners.rb +4 -6
- data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +80 -0
- data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +20 -0
- data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +16 -0
- data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +43 -0
- data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +26 -0
- data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +42 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +37 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +44 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +16 -0
- data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +43 -0
- data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/in_list_semver_matcher.rb +36 -0
- data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_matcher.rb +43 -0
- data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/matcher.rb +52 -0
- data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +29 -0
- data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +47 -0
- data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +22 -0
- data/lib/splitclient-rb/engine/matchers/prerequisites_matcher.rb +31 -0
- data/lib/splitclient-rb/engine/matchers/rule_based_segment_matcher.rb +78 -0
- data/lib/splitclient-rb/engine/matchers/semver.rb +201 -0
- data/lib/splitclient-rb/engine/matchers/set_matcher.rb +27 -0
- data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +40 -0
- data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +66 -0
- data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +66 -0
- data/lib/splitclient-rb/engine/models/evaluation_options.rb +9 -0
- data/lib/splitclient-rb/engine/models/event_active_subscriptions.rb +14 -0
- data/lib/splitclient-rb/engine/models/events_metadata.rb +10 -0
- data/lib/splitclient-rb/engine/models/fallback_treatment.rb +11 -0
- data/lib/splitclient-rb/engine/models/fallback_treatments_configuration.rb +36 -0
- data/lib/splitclient-rb/engine/models/label.rb +10 -0
- data/lib/splitclient-rb/engine/models/sdk_event.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_event_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event.rb +8 -0
- data/lib/splitclient-rb/engine/models/sdk_internal_event_notification.rb +16 -0
- data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/split.rb +17 -0
- data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
- data/lib/splitclient-rb/engine/models/treatment.rb +3 -0
- data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
- data/lib/splitclient-rb/engine/parser/condition.rb +271 -0
- data/lib/splitclient-rb/engine/parser/evaluator.rb +112 -0
- data/lib/{splitclient-engine → splitclient-rb/engine}/parser/partition.rb +2 -4
- data/lib/splitclient-rb/engine/push_manager.rb +66 -0
- data/lib/splitclient-rb/engine/status_manager.rb +39 -0
- data/lib/splitclient-rb/engine/sync_manager.rb +183 -0
- data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
- data/lib/splitclient-rb/exceptions.rb +26 -0
- data/lib/splitclient-rb/helpers/decryption_helper.rb +25 -0
- data/lib/splitclient-rb/helpers/evaluator_helper.rb +37 -0
- data/lib/splitclient-rb/helpers/repository_helper.rb +61 -0
- data/lib/splitclient-rb/helpers/thread_helper.rb +24 -0
- data/lib/splitclient-rb/helpers/util.rb +26 -0
- data/lib/splitclient-rb/managers/split_manager.rb +121 -0
- data/lib/splitclient-rb/spec.rb +9 -0
- data/lib/splitclient-rb/split_config.rb +591 -47
- data/lib/splitclient-rb/split_factory.rb +284 -0
- data/lib/splitclient-rb/split_factory_builder.rb +7 -0
- data/lib/splitclient-rb/split_factory_registry.rb +63 -0
- data/lib/splitclient-rb/split_logger.rb +23 -0
- data/lib/splitclient-rb/sse/event_source/client.rb +264 -0
- data/lib/splitclient-rb/sse/event_source/event_parser.rb +65 -0
- data/lib/splitclient-rb/sse/event_source/event_types.rb +15 -0
- data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
- data/lib/splitclient-rb/sse/notification_manager_keeper.rb +84 -0
- data/lib/splitclient-rb/sse/notification_processor.rb +48 -0
- data/lib/splitclient-rb/sse/sse_handler.rb +44 -0
- data/lib/splitclient-rb/sse/workers/segments_worker.rb +62 -0
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +153 -0
- data/lib/splitclient-rb/telemetry/domain/constants.rb +48 -0
- data/lib/splitclient-rb/telemetry/domain/structs.rb +35 -0
- data/lib/splitclient-rb/telemetry/evaluation_consumer.rb +14 -0
- data/lib/splitclient-rb/telemetry/evaluation_producer.rb +21 -0
- data/lib/splitclient-rb/telemetry/init_consumer.rb +14 -0
- data/lib/splitclient-rb/telemetry/init_producer.rb +19 -0
- data/lib/splitclient-rb/telemetry/memory/memory_evaluation_consumer.rb +32 -0
- data/lib/splitclient-rb/telemetry/memory/memory_evaluation_producer.rb +24 -0
- data/lib/splitclient-rb/telemetry/memory/memory_init_consumer.rb +28 -0
- data/lib/splitclient-rb/telemetry/memory/memory_init_producer.rb +34 -0
- data/lib/splitclient-rb/telemetry/memory/memory_runtime_consumer.rb +119 -0
- data/lib/splitclient-rb/telemetry/memory/memory_runtime_producer.rb +87 -0
- data/lib/splitclient-rb/telemetry/memory/memory_synchronizer.rb +213 -0
- data/lib/splitclient-rb/telemetry/redis/redis_evaluation_producer.rb +38 -0
- data/lib/splitclient-rb/telemetry/redis/redis_init_producer.rb +37 -0
- data/lib/splitclient-rb/telemetry/redis/redis_synchronizer.rb +27 -0
- data/lib/splitclient-rb/telemetry/runtime_consumer.rb +25 -0
- data/lib/splitclient-rb/telemetry/runtime_producer.rb +25 -0
- data/lib/splitclient-rb/telemetry/storages/memory.rb +159 -0
- data/lib/splitclient-rb/telemetry/sync_task.rb +36 -0
- data/lib/splitclient-rb/telemetry/synchronizer.rb +33 -0
- data/lib/splitclient-rb/utilitites.rb +49 -0
- data/lib/splitclient-rb/validators.rb +361 -0
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +177 -25
- data/sonar-project.properties +6 -0
- data/splitclient-rb.gemspec +53 -26
- data/tasks/benchmark_get_treatment.rake +31 -17
- data/tasks/irb.rake +6 -0
- metadata +411 -86
- data/NEWS +0 -9
- data/lib/splitclient-cache/local_store.rb +0 -45
- data/lib/splitclient-engine/impressions/impressions.rb +0 -79
- data/lib/splitclient-engine/matchers/between_matcher.rb +0 -48
- data/lib/splitclient-engine/matchers/combining_matcher.rb +0 -94
- data/lib/splitclient-engine/matchers/equal_to_matcher.rb +0 -48
- data/lib/splitclient-engine/matchers/greater_than_or_equal_to_matcher.rb +0 -48
- data/lib/splitclient-engine/matchers/less_than_or_equal_to_matcher.rb +0 -48
- data/lib/splitclient-engine/matchers/negation_matcher.rb +0 -54
- data/lib/splitclient-engine/matchers/user_defined_segment_matcher.rb +0 -61
- data/lib/splitclient-engine/matchers/whitelist_matcher.rb +0 -64
- data/lib/splitclient-engine/metrics/binary_search_latency_tracker.rb +0 -122
- data/lib/splitclient-engine/metrics/metrics.rb +0 -158
- data/lib/splitclient-engine/parser/condition.rb +0 -143
- data/lib/splitclient-engine/parser/segment.rb +0 -84
- data/lib/splitclient-engine/parser/segment_parser.rb +0 -46
- data/lib/splitclient-engine/parser/split.rb +0 -68
- data/lib/splitclient-engine/parser/split_adapter.rb +0 -433
- data/lib/splitclient-engine/parser/split_parser.rb +0 -139
- data/lib/splitclient-engine/partitions/treatments.rb +0 -40
- data/lib/splitclient-rb/split_client.rb +0 -156
- data/lib/splitclient-rb_utilitites.rb +0 -26
- data/tasks/console.rake +0 -4
- 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
|