splitclient-rb 6.3.0 → 8.11.0
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 +4 -4
- 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 +4 -0
- data/.rubocop.yml +46 -3
- data/CHANGES.txt +158 -11
- data/CONTRIBUTORS-GUIDE.md +49 -0
- data/LICENSE +169 -13
- data/NOTICE.txt +5 -0
- data/README.md +67 -27
- data/Rakefile +1 -8
- data/ext/murmurhash/3_x64_128.c +117 -0
- data/ext/murmurhash/murmurhash.c +5 -1
- data/lib/murmurhash/murmurhash.jar +0 -0
- data/lib/splitclient-rb/cache/adapters/cache_adapter.rb +3 -3
- data/lib/splitclient-rb/cache/adapters/memory_adapters/map_adapter.rb +4 -0
- data/lib/splitclient-rb/cache/adapters/memory_adapters/queue_adapter.rb +7 -0
- data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +12 -4
- 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 +26 -14
- data/lib/splitclient-rb/cache/repositories/events/redis_repository.rb +9 -14
- data/lib/splitclient-rb/cache/repositories/events_repository.rb +31 -10
- 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 +22 -23
- data/lib/splitclient-rb/cache/repositories/impressions/redis_repository.rb +15 -22
- data/lib/splitclient-rb/cache/repositories/impressions_repository.rb +6 -31
- data/lib/splitclient-rb/cache/repositories/repository.rb +6 -5
- data/lib/splitclient-rb/cache/repositories/rule_based_segments_repository.rb +136 -0
- data/lib/splitclient-rb/cache/repositories/segments_repository.rb +46 -6
- data/lib/splitclient-rb/cache/repositories/splits_repository.rb +232 -43
- data/lib/splitclient-rb/cache/routers/impression_router.rb +24 -22
- data/lib/splitclient-rb/cache/senders/events_sender.rb +12 -29
- 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 +27 -13
- data/lib/splitclient-rb/cache/senders/impressions_sender.rb +11 -25
- 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 +385 -138
- data/lib/splitclient-rb/constants.rb +16 -0
- data/lib/splitclient-rb/engine/api/client.rb +38 -43
- data/lib/splitclient-rb/engine/api/events.rb +19 -11
- data/lib/splitclient-rb/engine/api/faraday_middleware/gzip.rb +1 -0
- data/lib/splitclient-rb/engine/api/impressions.rb +49 -14
- data/lib/splitclient-rb/engine/api/segments.rb +31 -24
- data/lib/splitclient-rb/engine/api/splits.rb +108 -33
- data/lib/splitclient-rb/engine/api/telemetry_api.rb +47 -0
- data/lib/splitclient-rb/engine/auth_api_client.rb +96 -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-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-rb/engine/matchers/all_keys_matcher.rb +1 -1
- data/lib/splitclient-rb/engine/matchers/between_matcher.rb +7 -5
- data/lib/splitclient-rb/engine/matchers/between_semver_matcher.rb +33 -0
- data/lib/splitclient-rb/engine/matchers/combining_matcher.rb +10 -8
- data/lib/splitclient-rb/engine/matchers/contains_all_matcher.rb +2 -6
- data/lib/splitclient-rb/engine/matchers/contains_any_matcher.rb +1 -5
- data/lib/splitclient-rb/engine/matchers/contains_matcher.rb +7 -5
- data/lib/splitclient-rb/engine/matchers/dependency_matcher.rb +6 -5
- data/lib/splitclient-rb/engine/matchers/ends_with_matcher.rb +5 -4
- data/lib/splitclient-rb/engine/matchers/equal_to_boolean_matcher.rb +3 -2
- data/lib/splitclient-rb/engine/matchers/equal_to_matcher.rb +6 -4
- data/lib/splitclient-rb/engine/matchers/equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/equal_to_set_matcher.rb +1 -5
- data/lib/splitclient-rb/engine/matchers/greater_than_or_equal_to_matcher.rb +6 -4
- 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 +6 -4
- data/lib/splitclient-rb/engine/matchers/less_than_or_equal_to_semver_matcher.rb +28 -0
- data/lib/splitclient-rb/engine/matchers/matcher.rb +22 -0
- data/lib/splitclient-rb/engine/matchers/matches_string_matcher.rb +3 -2
- data/lib/splitclient-rb/engine/matchers/negation_matcher.rb +3 -2
- data/lib/splitclient-rb/engine/matchers/part_of_set_matcher.rb +2 -6
- 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 +2 -1
- data/lib/splitclient-rb/engine/matchers/starts_with_matcher.rb +4 -3
- data/lib/splitclient-rb/engine/matchers/user_defined_segment_matcher.rb +3 -2
- data/lib/splitclient-rb/engine/matchers/whitelist_matcher.rb +7 -5
- data/lib/splitclient-rb/engine/metrics/binary_search_latency_tracker.rb +3 -65
- 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 +3 -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 +14 -0
- data/lib/splitclient-rb/engine/models/segment_type.rb +4 -0
- data/lib/splitclient-rb/engine/models/split_http_response.rb +19 -0
- data/lib/splitclient-rb/engine/models/valid_sdk_event.rb +14 -0
- data/lib/splitclient-rb/engine/parser/condition.rb +81 -20
- data/lib/splitclient-rb/engine/parser/evaluator.rb +40 -51
- 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 +180 -0
- data/lib/splitclient-rb/engine/synchronizer.rb +231 -0
- data/lib/splitclient-rb/exceptions.rb +20 -1
- 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 +58 -20
- data/lib/splitclient-rb/spec.rb +9 -0
- data/lib/splitclient-rb/split_config.rb +336 -54
- data/lib/splitclient-rb/split_factory.rb +219 -33
- data/lib/splitclient-rb/split_factory_builder.rb +1 -22
- data/lib/splitclient-rb/split_factory_registry.rb +63 -0
- data/lib/splitclient-rb/split_logger.rb +9 -10
- data/lib/splitclient-rb/sse/event_source/client.rb +263 -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 +149 -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 +8 -0
- data/lib/splitclient-rb/validators.rb +142 -38
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +101 -16
- data/sonar-project.properties +6 -0
- data/splitclient-rb.gemspec +28 -23
- metadata +262 -82
- data/.travis.yml +0 -11
- data/Appraisals +0 -10
- data/Detailed-README.md +0 -588
- data/NEWS +0 -141
- data/lib/splitclient-rb/cache/repositories/metrics/memory_repository.rb +0 -127
- data/lib/splitclient-rb/cache/repositories/metrics/redis_repository.rb +0 -96
- data/lib/splitclient-rb/cache/repositories/metrics_repository.rb +0 -21
- data/lib/splitclient-rb/cache/senders/metrics_sender.rb +0 -56
- data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +0 -46
- data/lib/splitclient-rb/cache/stores/segment_store.rb +0 -81
- data/lib/splitclient-rb/cache/stores/split_store.rb +0 -102
- data/lib/splitclient-rb/clients/localhost_split_client.rb +0 -183
- data/lib/splitclient-rb/engine/api/faraday_adapter/patched_net_http_persistent.rb +0 -46
- data/lib/splitclient-rb/engine/api/metrics.rb +0 -60
- data/lib/splitclient-rb/engine/metrics/metrics.rb +0 -80
- data/lib/splitclient-rb/engine/parser/split_adapter.rb +0 -81
- data/lib/splitclient-rb/localhost_split_factory.rb +0 -13
- data/lib/splitclient-rb/localhost_utils.rb +0 -59
- data/lib/splitclient-rb/managers/localhost_split_manager.rb +0 -60
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
require 'socket'
|
|
4
|
+
require 'openssl'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'timeout'
|
|
7
|
+
|
|
8
|
+
module SplitIoClient
|
|
9
|
+
module SSE
|
|
10
|
+
module EventSource
|
|
11
|
+
class Client
|
|
12
|
+
DEFAULT_READ_TIMEOUT = 70
|
|
13
|
+
CONNECT_TIMEOUT = 30_000
|
|
14
|
+
OK_CODE = 200
|
|
15
|
+
KEEP_ALIVE_RESPONSE = "c\r\n:keepalive\n\n\r\n".freeze
|
|
16
|
+
ERROR_EVENT_TYPE = 'error'.freeze
|
|
17
|
+
|
|
18
|
+
def initialize(config,
|
|
19
|
+
api_key,
|
|
20
|
+
telemetry_runtime_producer,
|
|
21
|
+
event_parser,
|
|
22
|
+
notification_manager_keeper,
|
|
23
|
+
notification_processor,
|
|
24
|
+
status_queue,
|
|
25
|
+
read_timeout: DEFAULT_READ_TIMEOUT)
|
|
26
|
+
@config = config
|
|
27
|
+
@api_key = api_key
|
|
28
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
29
|
+
@event_parser = event_parser
|
|
30
|
+
@notification_manager_keeper = notification_manager_keeper
|
|
31
|
+
@notification_processor = notification_processor
|
|
32
|
+
@status_queue = status_queue
|
|
33
|
+
@read_timeout = read_timeout
|
|
34
|
+
@connected = Concurrent::AtomicBoolean.new(false)
|
|
35
|
+
@first_event = Concurrent::AtomicBoolean.new(true)
|
|
36
|
+
@socket = nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def close(status = nil)
|
|
40
|
+
unless connected?
|
|
41
|
+
@config.logger.debug('SSEClient already disconected.') if @config.debug_enabled
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
@config.logger.debug("Closing SSEClient socket") if @config.debug_enabled
|
|
45
|
+
|
|
46
|
+
push_status(status)
|
|
47
|
+
@connected.make_false
|
|
48
|
+
@socket.sync_close = true if @socket.is_a? OpenSSL::SSL::SSLSocket
|
|
49
|
+
@socket.close
|
|
50
|
+
@config.logger.debug("SSEClient socket state #{@socket.state}") if @socket.is_a? OpenSSL::SSL::SSLSocket && @config.debug_enabled
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
@config.logger.error("SSEClient close Error: #{e.inspect}")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def start(url)
|
|
56
|
+
if connected?
|
|
57
|
+
@config.logger.debug('SSEClient already running.') if @config.debug_enabled
|
|
58
|
+
return true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@uri = URI(url)
|
|
62
|
+
latch = Concurrent::CountDownLatch.new(1)
|
|
63
|
+
connect_thread(latch)
|
|
64
|
+
|
|
65
|
+
return false unless latch.wait(CONNECT_TIMEOUT)
|
|
66
|
+
|
|
67
|
+
connected?
|
|
68
|
+
rescue StandardError => e
|
|
69
|
+
@config.logger.error("SSEClient start Error: #{e.inspect}")
|
|
70
|
+
connected?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def connected?
|
|
74
|
+
@connected.value
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def connect_thread(latch)
|
|
80
|
+
@config.threads[:connect_stream] = Thread.new do
|
|
81
|
+
@config.logger.info('Starting connect_stream thread ...')
|
|
82
|
+
new_status = connect_stream(latch)
|
|
83
|
+
push_status(new_status)
|
|
84
|
+
@config.logger.info('connect_stream thread finished.')
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def connect_stream(latch)
|
|
89
|
+
return Constants::PUSH_NONRETRYABLE_ERROR unless socket_write(latch)
|
|
90
|
+
while connected? || @first_event.value
|
|
91
|
+
begin
|
|
92
|
+
if IO.select([@socket], nil, nil, @read_timeout)
|
|
93
|
+
begin
|
|
94
|
+
partial_data = @socket.readpartial(10_000)
|
|
95
|
+
read_first_event(partial_data, latch)
|
|
96
|
+
|
|
97
|
+
raise 'eof exception' if partial_data == :eof
|
|
98
|
+
rescue IO::WaitReadable => e
|
|
99
|
+
@config.logger.debug("SSE client IO::WaitReadable transient error: #{e.inspect}") if @config.debug_enabled
|
|
100
|
+
IO.select([@socket], nil, nil, @read_timeout)
|
|
101
|
+
retry
|
|
102
|
+
rescue Errno::EAGAIN => e
|
|
103
|
+
@config.logger.debug("SSE client transient error: #{e.inspect}") if @config.debug_enabled
|
|
104
|
+
IO.select([@socket], nil, nil, @read_timeout)
|
|
105
|
+
retry
|
|
106
|
+
rescue Errno::ETIMEDOUT => e
|
|
107
|
+
@config.logger.error("SSE read operation timed out!: #{e.inspect}")
|
|
108
|
+
return Constants::PUSH_RETRYABLE_ERROR
|
|
109
|
+
rescue EOFError => e
|
|
110
|
+
@config.logger.error("SSE read operation EOF Exception!: #{e.inspect}")
|
|
111
|
+
raise 'eof exception'
|
|
112
|
+
rescue Errno::EBADF, IOError => e
|
|
113
|
+
@config.logger.error("SSE read operation EBADF or IOError: #{e.inspect}")
|
|
114
|
+
return Constants::PUSH_RETRYABLE_ERROR
|
|
115
|
+
rescue StandardError => e
|
|
116
|
+
@config.logger.error("SSE read operation StandardError: #{e.inspect}")
|
|
117
|
+
return nil if ENV['SPLITCLIENT_ENV'] == 'test'
|
|
118
|
+
|
|
119
|
+
@config.logger.error("Error reading partial data: #{e.inspect}")
|
|
120
|
+
return Constants::PUSH_RETRYABLE_ERROR
|
|
121
|
+
end
|
|
122
|
+
else
|
|
123
|
+
@config.logger.error("SSE read operation timed out, no data available.")
|
|
124
|
+
return Constants::PUSH_RETRYABLE_ERROR
|
|
125
|
+
end
|
|
126
|
+
rescue Errno::EBADF
|
|
127
|
+
@config.logger.debug("SSE socket is not connected (Errno::EBADF)") if @config.debug_enabled
|
|
128
|
+
break
|
|
129
|
+
rescue RuntimeError
|
|
130
|
+
raise 'eof exception'
|
|
131
|
+
rescue Exception => e
|
|
132
|
+
@config.logger.debug("SSE socket is not connected: #{e.inspect}") if @config.debug_enabled
|
|
133
|
+
break
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
process_data(partial_data)
|
|
137
|
+
end
|
|
138
|
+
@config.logger.info("SSE read operation exited: #{connected?}")
|
|
139
|
+
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def socket_write(latch)
|
|
144
|
+
@first_event.make_true
|
|
145
|
+
@socket = socket_connect
|
|
146
|
+
@socket.puts(build_request(@uri))
|
|
147
|
+
true
|
|
148
|
+
rescue StandardError => e
|
|
149
|
+
@config.logger.error("Error during connecting to #{@uri.host}. Error: #{e.inspect}")
|
|
150
|
+
latch.count_down
|
|
151
|
+
false
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def read_first_event(data, latch)
|
|
155
|
+
return unless @first_event.value
|
|
156
|
+
|
|
157
|
+
response_code = @event_parser.first_event(data)
|
|
158
|
+
@config.logger.debug("SSE client first event code: #{response_code}") if @config.debug_enabled
|
|
159
|
+
|
|
160
|
+
error_event = false
|
|
161
|
+
events = @event_parser.parse(data)
|
|
162
|
+
events.each { |e| error_event = true if e.event_type == ERROR_EVENT_TYPE }
|
|
163
|
+
@first_event.make_false
|
|
164
|
+
|
|
165
|
+
if response_code == OK_CODE && !error_event
|
|
166
|
+
@connected.make_true
|
|
167
|
+
@config.logger.debug("SSE client first event Connected is true") if @config.debug_enabled
|
|
168
|
+
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::SSE_CONNECTION_ESTABLISHED, nil)
|
|
169
|
+
push_status(Constants::PUSH_CONNECTED)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
latch.count_down
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def socket_connect
|
|
176
|
+
tcp_socket = TCPSocket.new(@uri.host, @uri.port)
|
|
177
|
+
if @uri.scheme.casecmp('https').zero?
|
|
178
|
+
begin
|
|
179
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
180
|
+
ssl_socket = OpenSSL::SSL::SSLSocket.new(tcp_socket, ssl_context)
|
|
181
|
+
ssl_socket.hostname = @uri.host
|
|
182
|
+
|
|
183
|
+
begin
|
|
184
|
+
ssl_socket.connect_nonblock
|
|
185
|
+
rescue IO::WaitReadable
|
|
186
|
+
IO.select([ssl_socket])
|
|
187
|
+
retry
|
|
188
|
+
rescue IO::WaitWritable
|
|
189
|
+
IO.select(nil, [ssl_socket])
|
|
190
|
+
retry
|
|
191
|
+
end
|
|
192
|
+
return ssl_socket
|
|
193
|
+
|
|
194
|
+
rescue Exception => e
|
|
195
|
+
@config.logger.error("socket connect error: #{e.inspect}")
|
|
196
|
+
return nil
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
tcp_socket
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def process_data(partial_data)
|
|
204
|
+
@config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
|
|
205
|
+
return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE
|
|
206
|
+
|
|
207
|
+
events = @event_parser.parse(partial_data)
|
|
208
|
+
events.each { |event| process_event(event) }
|
|
209
|
+
rescue StandardError => e
|
|
210
|
+
@config.logger.error("process_data error: #{e.inspect}")
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def build_request(uri)
|
|
214
|
+
req = "GET #{uri.request_uri} HTTP/1.1\r\n"
|
|
215
|
+
req << "Host: #{uri.host}\r\n"
|
|
216
|
+
req << "Accept: text/event-stream\r\n"
|
|
217
|
+
req << "SplitSDKVersion: #{@config.language}-#{@config.version}\r\n"
|
|
218
|
+
req << "SplitSDKMachineIP: #{@config.machine_ip}\r\n"
|
|
219
|
+
req << "SplitSDKMachineName: #{@config.machine_name}\r\n"
|
|
220
|
+
req << "SplitSDKClientKey: #{@api_key.split(//).last(4).join}\r\n" unless @api_key.nil?
|
|
221
|
+
req << "Cache-Control: no-cache\r\n\r\n"
|
|
222
|
+
@config.logger.debug("Request info: #{req}") if @config.debug_enabled
|
|
223
|
+
req
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def process_event(event)
|
|
227
|
+
case event.event_type
|
|
228
|
+
when ERROR_EVENT_TYPE
|
|
229
|
+
dispatch_error(event)
|
|
230
|
+
else
|
|
231
|
+
dispatch_event(event)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
def dispatch_error(event)
|
|
236
|
+
@config.logger.error("Event error: #{event.event_type}, #{event.data}")
|
|
237
|
+
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::ABLY_ERROR, event.data['code'])
|
|
238
|
+
|
|
239
|
+
if event.data['code'] >= 40_140 && event.data['code'] <= 40_149
|
|
240
|
+
close(Constants::PUSH_RETRYABLE_ERROR)
|
|
241
|
+
elsif event.data['code'] >= 40_000 && event.data['code'] <= 49_999
|
|
242
|
+
close(Constants::PUSH_NONRETRYABLE_ERROR)
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def dispatch_event(event)
|
|
247
|
+
if event.occupancy?
|
|
248
|
+
@notification_manager_keeper.handle_incoming_occupancy_event(event)
|
|
249
|
+
else
|
|
250
|
+
@notification_processor.process(event)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def push_status(status)
|
|
255
|
+
return if status.nil?
|
|
256
|
+
|
|
257
|
+
@config.logger.debug("Pushing new sse status: #{status}") if @config.debug_enabled
|
|
258
|
+
@status_queue.push(status)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module SSE
|
|
5
|
+
module EventSource
|
|
6
|
+
class EventParser
|
|
7
|
+
BAD_REQUEST_CODE = 400
|
|
8
|
+
|
|
9
|
+
def initialize(config)
|
|
10
|
+
@config = config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def parse(raw_event)
|
|
14
|
+
type = nil
|
|
15
|
+
events = []
|
|
16
|
+
buffer = read_partial_data(raw_event)
|
|
17
|
+
|
|
18
|
+
buffer.each do |d|
|
|
19
|
+
splited_data = d.split(':')
|
|
20
|
+
|
|
21
|
+
case splited_data[0]
|
|
22
|
+
when 'event'
|
|
23
|
+
type = splited_data[1].strip
|
|
24
|
+
when 'data'
|
|
25
|
+
data = parse_event_data(d, type)
|
|
26
|
+
events << StreamData.new(type, data[:client_id], data[:data], data[:channel]) unless type.nil? || data[:data].nil?
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
events
|
|
31
|
+
rescue StandardError => e
|
|
32
|
+
@config.logger.debug("Error during parsing a event: #{e.inspect}") if @config.debug_enabled
|
|
33
|
+
[]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def first_event(raw_data)
|
|
37
|
+
raw_data.split("\n")[0].split(' ')[1].to_i
|
|
38
|
+
rescue StandardError => e
|
|
39
|
+
@config.logger.error("Error parsing first event: #{e.inspect}")
|
|
40
|
+
BAD_REQUEST_CODE
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def parse_event_data(data, type)
|
|
46
|
+
data_value = data.sub('data:', '')
|
|
47
|
+
event_data = JSON.parse(data_value.strip)
|
|
48
|
+
client_id = event_data['clientId']&.strip
|
|
49
|
+
channel = event_data['channel']&.strip
|
|
50
|
+
parsed_data = JSON.parse(event_data['data']) unless type == 'error'
|
|
51
|
+
parsed_data = event_data if type == 'error'
|
|
52
|
+
|
|
53
|
+
{ client_id: client_id, channel: channel, data: parsed_data }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def read_partial_data(data)
|
|
57
|
+
buffer = ''
|
|
58
|
+
buffer << data
|
|
59
|
+
buffer.chomp!
|
|
60
|
+
buffer.split("\n")
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module SSE
|
|
5
|
+
module EventSource
|
|
6
|
+
class EventTypes
|
|
7
|
+
SPLIT_UPDATE = 'SPLIT_UPDATE'
|
|
8
|
+
SPLIT_KILL = 'SPLIT_KILL'
|
|
9
|
+
SEGMENT_UPDATE = 'SEGMENT_UPDATE'
|
|
10
|
+
CONTROL = 'CONTROL'
|
|
11
|
+
RB_SEGMENT_UPDATE = 'RB_SEGMENT_UPDATE'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module SSE
|
|
5
|
+
module EventSource
|
|
6
|
+
class StreamData
|
|
7
|
+
attr_reader :event_type, :channel, :data, :client_id
|
|
8
|
+
|
|
9
|
+
def initialize(event_type, client_id, data, channel)
|
|
10
|
+
@event_type = event_type
|
|
11
|
+
@client_id = client_id
|
|
12
|
+
@data = data
|
|
13
|
+
@channel = channel&.gsub(SplitIoClient::Constants::OCCUPANCY_CHANNEL_PREFIX, '')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def occupancy?
|
|
17
|
+
@channel.include? 'control'
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'concurrent'
|
|
4
|
+
|
|
5
|
+
module SplitIoClient
|
|
6
|
+
module SSE
|
|
7
|
+
class NotificationManagerKeeper
|
|
8
|
+
DISABLED = 0
|
|
9
|
+
ENABLED = 1
|
|
10
|
+
PAUSED = 2
|
|
11
|
+
|
|
12
|
+
def initialize(config, telemetry_runtime_producer, status_queue)
|
|
13
|
+
@config = config
|
|
14
|
+
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
15
|
+
@status_queue = status_queue
|
|
16
|
+
@publisher_available = Concurrent::AtomicBoolean.new(true)
|
|
17
|
+
@publishers_pri = Concurrent::AtomicFixnum.new(2)
|
|
18
|
+
@publishers_sec = Concurrent::AtomicFixnum.new(2)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def handle_incoming_occupancy_event(event)
|
|
22
|
+
if event.data['type'] == 'CONTROL'
|
|
23
|
+
process_event_control(event.data['controlType'])
|
|
24
|
+
else
|
|
25
|
+
process_event_occupancy(event.channel, event.data['metrics']['publishers'])
|
|
26
|
+
end
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
@config.logger.error(e)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def process_event_control(type)
|
|
34
|
+
case type
|
|
35
|
+
when 'STREAMING_PAUSED'
|
|
36
|
+
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::STREAMING_STATUS, PAUSED)
|
|
37
|
+
push_status(Constants::PUSH_SUBSYSTEM_DOWN)
|
|
38
|
+
when 'STREAMING_RESUMED'
|
|
39
|
+
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::STREAMING_STATUS, ENABLED)
|
|
40
|
+
push_status(Constants::PUSH_SUBSYSTEM_READY) if @publisher_available.value
|
|
41
|
+
when 'STREAMING_DISABLED'
|
|
42
|
+
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::STREAMING_STATUS, DISABLED)
|
|
43
|
+
push_status(Constants::PUSH_SUBSYSTEM_OFF)
|
|
44
|
+
else
|
|
45
|
+
@config.logger.error("Incorrect event type: #{incoming_notification}") if @config.debug_enabled
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def process_event_occupancy(channel, publishers)
|
|
50
|
+
@config.logger.debug("Processed occupancy event with #{publishers} publishers. Channel: #{channel}") if @config.debug_enabled
|
|
51
|
+
|
|
52
|
+
update_publishers(channel, publishers)
|
|
53
|
+
|
|
54
|
+
if !are_publishers_available? && @publisher_available.value
|
|
55
|
+
@publisher_available.make_false
|
|
56
|
+
push_status(Constants::PUSH_SUBSYSTEM_DOWN)
|
|
57
|
+
elsif are_publishers_available? && !@publisher_available.value
|
|
58
|
+
@publisher_available.make_true
|
|
59
|
+
push_status(Constants::PUSH_SUBSYSTEM_READY)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def update_publishers(channel, publishers)
|
|
64
|
+
case channel
|
|
65
|
+
when Constants::CONTROL_PRI
|
|
66
|
+
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::OCCUPANCY_PRI, publishers)
|
|
67
|
+
@publishers_pri.value = publishers
|
|
68
|
+
when Constants::CONTROL_SEC
|
|
69
|
+
@telemetry_runtime_producer.record_streaming_event(Telemetry::Domain::Constants::OCCUPANCY_SEC, publishers)
|
|
70
|
+
@publishers_sec.value = publishers
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def are_publishers_available?
|
|
75
|
+
@publishers_pri.value.positive? || @publishers_sec.value.positive?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def push_status(status)
|
|
79
|
+
@config.logger.debug("Pushing occupancy status: #{status}") if @config.debug_enabled
|
|
80
|
+
@status_queue.push(status)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module SSE
|
|
5
|
+
class NotificationProcessor
|
|
6
|
+
def initialize(config, splits_worker, segments_worker)
|
|
7
|
+
@config = config
|
|
8
|
+
@splits_worker = splits_worker
|
|
9
|
+
@segments_worker = segments_worker
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def process(incoming_notification)
|
|
13
|
+
case incoming_notification.data['type']
|
|
14
|
+
when SSE::EventSource::EventTypes::SPLIT_UPDATE
|
|
15
|
+
process_split_update(incoming_notification)
|
|
16
|
+
when SSE::EventSource::EventTypes::RB_SEGMENT_UPDATE
|
|
17
|
+
process_split_update(incoming_notification)
|
|
18
|
+
when SSE::EventSource::EventTypes::SPLIT_KILL
|
|
19
|
+
process_split_kill(incoming_notification)
|
|
20
|
+
when SSE::EventSource::EventTypes::SEGMENT_UPDATE
|
|
21
|
+
process_segment_update(incoming_notification)
|
|
22
|
+
else
|
|
23
|
+
@config.logger.error("Incorrect event type: #{incoming_notification}")
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def process_split_update(notification)
|
|
30
|
+
@config.logger.debug("#{notification.event_type} notification received: #{notification}") if @config.debug_enabled
|
|
31
|
+
@splits_worker.add_to_queue(notification)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def process_split_kill(notification)
|
|
35
|
+
@config.logger.debug("SPLIT KILL notification received: #{notification}") if @config.debug_enabled
|
|
36
|
+
@splits_worker.add_to_queue(notification)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def process_segment_update(notification)
|
|
40
|
+
@config.logger.debug("SEGMENT UPDATE notification received: #{notification}") if @config.debug_enabled
|
|
41
|
+
change_number = notification.data['changeNumber']
|
|
42
|
+
segment_name = notification.data['segmentName']
|
|
43
|
+
|
|
44
|
+
@segments_worker.add_to_queue(change_number, segment_name)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module SSE
|
|
5
|
+
class SSEHandler
|
|
6
|
+
attr_reader :sse_client
|
|
7
|
+
|
|
8
|
+
def initialize(config,
|
|
9
|
+
splits_worker,
|
|
10
|
+
segments_worker,
|
|
11
|
+
sse_client)
|
|
12
|
+
@config = config
|
|
13
|
+
@splits_worker = splits_worker
|
|
14
|
+
@segments_worker = segments_worker
|
|
15
|
+
@sse_client = sse_client
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def start(token_jwt, channels)
|
|
19
|
+
@sse_client.start("#{@config.streaming_service_url}?channels=#{channels}&v=1.1&accessToken=#{token_jwt}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def stop
|
|
23
|
+
@sse_client.close(Constants::PUSH_FORCED_STOP)
|
|
24
|
+
stop_workers
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
@config.logger.debug("SSEHandler stop error: #{e.inspect}") if @config.debug_enabled
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def connected?
|
|
30
|
+
@sse_client&.connected? || false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def start_workers
|
|
34
|
+
@splits_worker.start
|
|
35
|
+
@segments_worker.start
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def stop_workers
|
|
39
|
+
@splits_worker.stop
|
|
40
|
+
@segments_worker.stop
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module SSE
|
|
5
|
+
module Workers
|
|
6
|
+
class SegmentsWorker
|
|
7
|
+
def initialize(synchronizer, config, segments_repository)
|
|
8
|
+
@synchronizer = synchronizer
|
|
9
|
+
@config = config
|
|
10
|
+
@segments_repository = segments_repository
|
|
11
|
+
@queue = Queue.new
|
|
12
|
+
@running = Concurrent::AtomicBoolean.new(false)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def add_to_queue(change_number, segment_name)
|
|
16
|
+
item = { change_number: change_number, segment_name: segment_name }
|
|
17
|
+
@config.logger.debug("SegmentsWorker add to queue #{item}") if @config.debug_enabled
|
|
18
|
+
@queue.push(item)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def start
|
|
22
|
+
if @running.value
|
|
23
|
+
@config.logger.debug('segments worker already running.') if @config.debug_enabled
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
@running.make_true
|
|
28
|
+
perform_thread
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def stop
|
|
32
|
+
unless @running.value
|
|
33
|
+
@config.logger.debug('segments worker not running.') if @config.debug_enabled
|
|
34
|
+
return
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@running.make_false
|
|
38
|
+
SplitIoClient::Helpers::ThreadHelper.stop(:segment_update_worker, @config)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def perform
|
|
44
|
+
while (item = @queue.pop)
|
|
45
|
+
segment_name = item[:segment_name]
|
|
46
|
+
cn = item[:change_number]
|
|
47
|
+
@config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}") if @config.debug_enabled
|
|
48
|
+
|
|
49
|
+
@synchronizer.fetch_segment(segment_name, cn)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def perform_thread
|
|
54
|
+
@config.threads[:segment_update_worker] = Thread.new do
|
|
55
|
+
@config.logger.debug('Starting segments worker ...') if @config.debug_enabled
|
|
56
|
+
perform
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|