splitclient-rb 7.2.0 → 7.2.3.pre.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/CHANGES.txt +7 -0
- data/lib/splitclient-rb.rb +1 -0
- data/lib/splitclient-rb/cache/adapters/redis_adapter.rb +1 -1
- data/lib/splitclient-rb/cache/senders/impressions_count_sender.rb +21 -21
- data/lib/splitclient-rb/constants.rb +6 -0
- data/lib/splitclient-rb/engine/push_manager.rb +8 -5
- data/lib/splitclient-rb/engine/sync_manager.rb +69 -32
- data/lib/splitclient-rb/engine/synchronizer.rb +5 -3
- data/lib/splitclient-rb/split_factory.rb +14 -10
- data/lib/splitclient-rb/sse/event_source/client.rb +68 -80
- data/lib/splitclient-rb/sse/event_source/event_parser.rb +64 -0
- data/lib/splitclient-rb/sse/notification_manager_keeper.rb +30 -25
- data/lib/splitclient-rb/sse/sse_handler.rb +6 -15
- data/lib/splitclient-rb/sse/workers/segments_worker.rb +21 -9
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +27 -9
- data/lib/splitclient-rb/version.rb +1 -1
- data/splitclient-rb.gemspec +1 -1
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f35241d91acb957b2e666ca2598021b66f8214d4d12980484b063723576dc13c
|
4
|
+
data.tar.gz: 1a833d613162de2aa18a3c925de083ddc2013bfe85630d57b2e9483a8364c357
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 231f86643247585e51cbb136790d71011764645edb87383c5641730b4e36952c1013dde5c06fbb46002c05b1fa07579a0b8579a3a4e859cc42862d0d2849316c
|
7
|
+
data.tar.gz: faff2962445b587f133d703ce19fec646a8495fbef7f34d8dd27d38614643bd517acf99d859b613220a4144c4f56be61c1ae048b526ef8e63a92134dfdcbda79
|
data/.rubocop.yml
CHANGED
data/CHANGES.txt
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
CHANGES
|
2
2
|
|
3
|
+
7.2.2 (Dec 18, 2020)
|
4
|
+
- Fixed issue: undefined local variable or method post_impressions_count
|
5
|
+
|
6
|
+
7.2.1 (Oct 23, 2020)
|
7
|
+
- Updated redis dependency to >= 4.2.2.
|
8
|
+
- Updated ably error handling.
|
9
|
+
|
3
10
|
7.2.0 (Sep 25, 2020)
|
4
11
|
- Added deduplication logic for impressions data.
|
5
12
|
- Now there are two modes for Impressions when the SDK is in standalone mode, OPTIMIZED (default) that only ships unique impressions and DEBUG for times where you need to send ALL impressions to debug an integration.
|
data/lib/splitclient-rb.rb
CHANGED
@@ -101,6 +101,7 @@ require 'splitclient-rb/redis_metrics_fixer'
|
|
101
101
|
# SSE
|
102
102
|
require 'splitclient-rb/sse/event_source/back_off'
|
103
103
|
require 'splitclient-rb/sse/event_source/client'
|
104
|
+
require 'splitclient-rb/sse/event_source/event_parser'
|
104
105
|
require 'splitclient-rb/sse/event_source/event_types'
|
105
106
|
require 'splitclient-rb/sse/event_source/stream_data'
|
106
107
|
require 'splitclient-rb/sse/workers/segments_worker'
|
@@ -40,32 +40,32 @@ module SplitIoClient
|
|
40
40
|
@config.logger.info('Posting impressions count due to shutdown')
|
41
41
|
end
|
42
42
|
end
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
def formatter(counts)
|
51
|
-
return if counts.empty?
|
45
|
+
def post_impressions_count
|
46
|
+
@impressions_api.post_count(formatter(@impression_counter.pop_all))
|
47
|
+
rescue StandardError => error
|
48
|
+
@config.log_found_exception(__method__.to_s, error)
|
49
|
+
end
|
52
50
|
|
53
|
-
|
51
|
+
def formatter(counts)
|
52
|
+
return if counts.empty?
|
54
53
|
|
55
|
-
|
56
|
-
key_splited = key.split('::')
|
57
|
-
|
58
|
-
formated_counts[:pf] << {
|
59
|
-
f: key_splited[0].to_s, # feature name
|
60
|
-
m: key_splited[1].to_i, # time frame
|
61
|
-
rc: value # count
|
62
|
-
}
|
63
|
-
end
|
54
|
+
formated_counts = {pf: []}
|
64
55
|
|
65
|
-
|
66
|
-
|
67
|
-
|
56
|
+
counts.each do |key, value|
|
57
|
+
key_splited = key.split('::')
|
58
|
+
|
59
|
+
formated_counts[:pf] << {
|
60
|
+
f: key_splited[0].to_s, # feature name
|
61
|
+
m: key_splited[1].to_i, # time frame
|
62
|
+
rc: value # count
|
63
|
+
}
|
68
64
|
end
|
65
|
+
|
66
|
+
formated_counts
|
67
|
+
rescue StandardError => error
|
68
|
+
@config.log_found_exception(__method__.to_s, error)
|
69
69
|
end
|
70
70
|
end
|
71
71
|
end
|
@@ -6,5 +6,11 @@ class SplitIoClient::Constants
|
|
6
6
|
CONTROL_SEC = 'control_sec'
|
7
7
|
OCCUPANCY_CHANNEL_PREFIX = '[?occupancy=metrics.publishers]'
|
8
8
|
FETCH_BACK_OFF_BASE_RETRIES = 1
|
9
|
+
PUSH_CONNECTED = 'PUSH_CONNECTED'
|
10
|
+
PUSH_RETRYABLE_ERROR = 'PUSH_RETRYABLE_ERROR'
|
11
|
+
PUSH_NONRETRYABLE_ERROR = 'PUSH_NONRETRYABLE_ERROR'
|
12
|
+
PUSH_SUBSYSTEM_DOWN = 'PUSH_SUBSYSTEM_DOWN'
|
13
|
+
PUSH_SUBSYSTEM_READY = 'PUSH_SUBSYSTEM_READY'
|
14
|
+
PUSH_SUBSYSTEM_OFF = 'PUSH_SUBSYSTEM_OFF'
|
9
15
|
end
|
10
16
|
|
@@ -15,15 +15,17 @@ module SplitIoClient
|
|
15
15
|
response = @auth_api_client.authenticate(@api_key)
|
16
16
|
|
17
17
|
@config.logger.debug("Auth service response push_enabled: #{response[:push_enabled]}") if @config.debug_enabled
|
18
|
-
|
19
|
-
|
18
|
+
|
19
|
+
if response[:push_enabled] && @sse_handler.start(response[:token], response[:channels])
|
20
20
|
schedule_next_token_refresh(response[:exp])
|
21
21
|
@back_off.reset
|
22
|
-
|
23
|
-
stop_sse
|
22
|
+
return true
|
24
23
|
end
|
25
24
|
|
25
|
+
stop_sse
|
26
|
+
|
26
27
|
schedule_next_token_refresh(@back_off.interval) if response[:retry]
|
28
|
+
false
|
27
29
|
rescue StandardError => e
|
28
30
|
@config.logger.error("start_sse: #{e.inspect}")
|
29
31
|
end
|
@@ -31,6 +33,7 @@ module SplitIoClient
|
|
31
33
|
def stop_sse
|
32
34
|
@sse_handler.process_disconnect if @sse_handler.sse_client.nil?
|
33
35
|
@sse_handler.stop
|
36
|
+
SplitIoClient::Helpers::ThreadHelper.stop(:schedule_next_token_refresh, @config)
|
34
37
|
end
|
35
38
|
|
36
39
|
private
|
@@ -41,7 +44,7 @@ module SplitIoClient
|
|
41
44
|
@config.logger.debug("schedule_next_token_refresh refresh in #{time} seconds.") if @config.debug_enabled
|
42
45
|
sleep(time)
|
43
46
|
@config.logger.debug('schedule_next_token_refresh starting ...') if @config.debug_enabled
|
44
|
-
|
47
|
+
@sse_handler.stop
|
45
48
|
start_sse
|
46
49
|
rescue StandardError => e
|
47
50
|
@config.logger.debug("schedule_next_token_refresh error: #{e.inspect}") if @config.debug_enabled
|
@@ -3,22 +3,15 @@
|
|
3
3
|
module SplitIoClient
|
4
4
|
module Engine
|
5
5
|
class SyncManager
|
6
|
-
include SplitIoClient::Cache::Fetchers
|
7
|
-
|
8
6
|
def initialize(
|
9
7
|
repositories,
|
10
8
|
api_key,
|
11
9
|
config,
|
12
|
-
|
10
|
+
synchronizer
|
13
11
|
)
|
14
|
-
|
15
|
-
segment_fetcher = SegmentFetcher.new(repositories[:segments], api_key, params[:metrics], config, params[:sdk_blocker])
|
16
|
-
sync_params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher, imp_counter: params[:impression_counter] }
|
17
|
-
|
18
|
-
@synchronizer = Synchronizer.new(repositories, api_key, config, params[:sdk_blocker], sync_params)
|
12
|
+
@synchronizer = synchronizer
|
19
13
|
notification_manager_keeper = SplitIoClient::SSE::NotificationManagerKeeper.new(config) do |manager|
|
20
|
-
manager.
|
21
|
-
manager.on_push_shutdown { process_push_shutdown }
|
14
|
+
manager.on_action { |action| process_action(action) }
|
22
15
|
end
|
23
16
|
@sse_handler = SplitIoClient::SSE::SSEHandler.new(
|
24
17
|
config,
|
@@ -27,11 +20,11 @@ module SplitIoClient
|
|
27
20
|
repositories[:segments],
|
28
21
|
notification_manager_keeper
|
29
22
|
) do |handler|
|
30
|
-
handler.
|
31
|
-
handler.on_disconnect { process_disconnect }
|
23
|
+
handler.on_action { |action| process_action(action) }
|
32
24
|
end
|
33
25
|
|
34
26
|
@push_manager = PushManager.new(config, @sse_handler, api_key)
|
27
|
+
@sse_connected = Concurrent::AtomicBoolean.new(false)
|
35
28
|
@config = config
|
36
29
|
end
|
37
30
|
|
@@ -49,10 +42,10 @@ module SplitIoClient
|
|
49
42
|
# Starts tasks if stream is enabled.
|
50
43
|
def start_stream
|
51
44
|
@config.logger.debug('Starting push mode ...')
|
52
|
-
|
45
|
+
sync_all_thread
|
53
46
|
@synchronizer.start_periodic_data_recording
|
54
47
|
|
55
|
-
|
48
|
+
start_sse_connection_thread
|
56
49
|
end
|
57
50
|
|
58
51
|
def start_poll
|
@@ -64,23 +57,24 @@ module SplitIoClient
|
|
64
57
|
end
|
65
58
|
|
66
59
|
# Starts thread which fetch splits and segments once and trigger task to periodic data recording.
|
67
|
-
def
|
60
|
+
def sync_all_thread
|
68
61
|
@config.threads[:sync_manager_start_stream] = Thread.new do
|
69
62
|
begin
|
70
63
|
@synchronizer.sync_all
|
71
64
|
rescue StandardError => e
|
72
|
-
@config.logger.error("
|
65
|
+
@config.logger.error("sync_all_thread error : #{e.inspect}")
|
73
66
|
end
|
74
67
|
end
|
75
68
|
end
|
76
69
|
|
77
70
|
# Starts thread which connect to sse and after that fetch splits and segments once.
|
78
|
-
def
|
71
|
+
def start_sse_connection_thread
|
79
72
|
@config.threads[:sync_manager_start_sse] = Thread.new do
|
80
73
|
begin
|
81
|
-
@push_manager.start_sse
|
74
|
+
connected = @push_manager.start_sse
|
75
|
+
@synchronizer.start_periodic_fetch unless connected
|
82
76
|
rescue StandardError => e
|
83
|
-
@config.logger.error("
|
77
|
+
@config.logger.error("start_sse_connection_thread error : #{e.inspect}")
|
84
78
|
end
|
85
79
|
end
|
86
80
|
end
|
@@ -89,33 +83,76 @@ module SplitIoClient
|
|
89
83
|
PhusionPassenger.on_event(:starting_worker_process) { |forked| start_stream if forked }
|
90
84
|
end
|
91
85
|
|
92
|
-
def
|
86
|
+
def process_action(action)
|
87
|
+
case action
|
88
|
+
when Constants::PUSH_CONNECTED
|
89
|
+
process_connected
|
90
|
+
when Constants::PUSH_RETRYABLE_ERROR
|
91
|
+
process_disconnect(true)
|
92
|
+
when Constants::PUSH_NONRETRYABLE_ERROR
|
93
|
+
process_disconnect(false)
|
94
|
+
when Constants::PUSH_SUBSYSTEM_DOWN
|
95
|
+
process_subsystem_down
|
96
|
+
when Constants::PUSH_SUBSYSTEM_READY
|
97
|
+
process_subsystem_ready
|
98
|
+
when Constants::PUSH_SUBSYSTEM_OFF
|
99
|
+
process_push_shutdown
|
100
|
+
else
|
101
|
+
@config.logger.debug('Incorrect action type.')
|
102
|
+
end
|
103
|
+
rescue StandardError => e
|
104
|
+
@config.logger.error("process_action error: #{e.inspect}")
|
105
|
+
end
|
106
|
+
|
107
|
+
def process_subsystem_ready
|
93
108
|
@synchronizer.stop_periodic_fetch
|
94
109
|
@synchronizer.sync_all
|
95
110
|
@sse_handler.start_workers
|
96
|
-
rescue StandardError => e
|
97
|
-
@config.logger.error("process_connected error: #{e.inspect}")
|
98
111
|
end
|
99
112
|
|
100
|
-
def
|
113
|
+
def process_subsystem_down
|
114
|
+
@sse_handler.stop_workers
|
115
|
+
@synchronizer.start_periodic_fetch
|
116
|
+
end
|
117
|
+
|
118
|
+
def process_push_shutdown
|
119
|
+
@push_manager.stop_sse
|
101
120
|
@sse_handler.stop_workers
|
102
121
|
@synchronizer.start_periodic_fetch
|
103
122
|
rescue StandardError => e
|
104
|
-
@config.logger.error("
|
123
|
+
@config.logger.error("process_push_shutdown error: #{e.inspect}")
|
105
124
|
end
|
106
125
|
|
107
|
-
def
|
108
|
-
|
109
|
-
|
126
|
+
def process_connected
|
127
|
+
if @sse_connected.value
|
128
|
+
@config.logger.debug('Streaming already connected.')
|
129
|
+
return
|
130
|
+
end
|
131
|
+
|
132
|
+
@sse_connected.make_true
|
133
|
+
@synchronizer.stop_periodic_fetch
|
134
|
+
@synchronizer.sync_all
|
135
|
+
@sse_handler.start_workers
|
110
136
|
rescue StandardError => e
|
111
|
-
@config.logger.error("
|
137
|
+
@config.logger.error("process_connected error: #{e.inspect}")
|
112
138
|
end
|
113
139
|
|
114
|
-
def
|
115
|
-
@
|
116
|
-
|
140
|
+
def process_disconnect(reconnect)
|
141
|
+
unless @sse_connected.value
|
142
|
+
@config.logger.debug('Streaming already disconnected.')
|
143
|
+
return
|
144
|
+
end
|
145
|
+
|
146
|
+
@sse_connected.make_false
|
147
|
+
@sse_handler.stop_workers
|
148
|
+
@synchronizer.start_periodic_fetch
|
149
|
+
|
150
|
+
if reconnect
|
151
|
+
@synchronizer.sync_all
|
152
|
+
@push_manager.start_sse
|
153
|
+
end
|
117
154
|
rescue StandardError => e
|
118
|
-
@config.logger.error("
|
155
|
+
@config.logger.error("process_disconnect error: #{e.inspect}")
|
119
156
|
end
|
120
157
|
end
|
121
158
|
end
|
@@ -28,9 +28,11 @@ module SplitIoClient
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def sync_all
|
31
|
-
@config.
|
32
|
-
|
33
|
-
|
31
|
+
@config.threads[:sync_all_thread] = Thread.new do
|
32
|
+
@config.logger.debug('Synchronizing Splits and Segments ...') if @config.debug_enabled
|
33
|
+
fetch_splits
|
34
|
+
fetch_segments
|
35
|
+
end
|
34
36
|
end
|
35
37
|
|
36
38
|
def start_periodic_data_recording
|
@@ -7,6 +7,7 @@ module SplitIoClient
|
|
7
7
|
include SplitIoClient::Cache::Repositories
|
8
8
|
include SplitIoClient::Cache::Stores
|
9
9
|
include SplitIoClient::Cache::Senders
|
10
|
+
include SplitIoClient::Cache::Fetchers
|
10
11
|
|
11
12
|
attr_reader :adapter, :client, :manager, :config
|
12
13
|
|
@@ -53,8 +54,12 @@ module SplitIoClient
|
|
53
54
|
if @config.localhost_mode
|
54
55
|
start_localhost_components
|
55
56
|
else
|
56
|
-
|
57
|
-
|
57
|
+
split_fetcher = SplitFetcher.new(@splits_repository, @api_key, @metrics, config, @sdk_blocker)
|
58
|
+
segment_fetcher = SegmentFetcher.new(@segments_repository, @api_key, @metrics, config, @sdk_blocker)
|
59
|
+
params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher, imp_counter: @impression_counter }
|
60
|
+
|
61
|
+
synchronizer = SplitIoClient::Engine::Synchronizer.new(repositories, @api_key, @config, @sdk_blocker, params)
|
62
|
+
SplitIoClient::Engine::SyncManager.new(repositories, @api_key, @config, synchronizer).start
|
58
63
|
end
|
59
64
|
end
|
60
65
|
|
@@ -125,14 +130,13 @@ module SplitIoClient
|
|
125
130
|
end
|
126
131
|
|
127
132
|
def repositories
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
repos
|
133
|
+
{
|
134
|
+
splits: @splits_repository,
|
135
|
+
segments: @segments_repository,
|
136
|
+
impressions: @impressions_repository,
|
137
|
+
events: @events_repository,
|
138
|
+
metrics: @metrics_repository
|
139
|
+
}
|
136
140
|
end
|
137
141
|
|
138
142
|
def start_localhost_components
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: false
|
2
2
|
|
3
|
-
require 'concurrent/atomics'
|
4
3
|
require 'socketry'
|
5
4
|
require 'uri'
|
6
5
|
|
@@ -9,16 +8,19 @@ module SplitIoClient
|
|
9
8
|
module EventSource
|
10
9
|
class Client
|
11
10
|
DEFAULT_READ_TIMEOUT = 70
|
11
|
+
CONNECT_TIMEOUT = 30_000
|
12
|
+
OK_CODE = 200
|
12
13
|
KEEP_ALIVE_RESPONSE = "c\r\n:keepalive\n\n\r\n".freeze
|
14
|
+
ERROR_EVENT_TYPE = 'error'.freeze
|
13
15
|
|
14
16
|
def initialize(config, read_timeout: DEFAULT_READ_TIMEOUT)
|
15
17
|
@config = config
|
16
18
|
@read_timeout = read_timeout
|
17
19
|
@connected = Concurrent::AtomicBoolean.new(false)
|
20
|
+
@first_event = Concurrent::AtomicBoolean.new(true)
|
18
21
|
@socket = nil
|
19
|
-
@
|
20
|
-
|
21
|
-
@on = { event: ->(_) {}, connected: ->(_) {}, disconnect: ->(_) {} }
|
22
|
+
@event_parser = SSE::EventSource::EventParser.new(config)
|
23
|
+
@on = { event: ->(_) {}, action: ->(_) {} }
|
22
24
|
|
23
25
|
yield self if block_given?
|
24
26
|
end
|
@@ -27,16 +29,12 @@ module SplitIoClient
|
|
27
29
|
@on[:event] = action
|
28
30
|
end
|
29
31
|
|
30
|
-
def
|
31
|
-
@on[:
|
32
|
-
end
|
33
|
-
|
34
|
-
def on_disconnect(&action)
|
35
|
-
@on[:disconnect] = action
|
32
|
+
def on_action(&action)
|
33
|
+
@on[:action] = action
|
36
34
|
end
|
37
35
|
|
38
|
-
def close
|
39
|
-
|
36
|
+
def close(action = Constants::PUSH_NONRETRYABLE_ERROR)
|
37
|
+
dispatch_action(action)
|
40
38
|
@connected.make_false
|
41
39
|
SplitIoClient::Helpers::ThreadHelper.stop(:connect_stream, @config)
|
42
40
|
@socket&.close
|
@@ -46,10 +44,16 @@ module SplitIoClient
|
|
46
44
|
|
47
45
|
def start(url)
|
48
46
|
@uri = URI(url)
|
47
|
+
latch = Concurrent::CountDownLatch.new(1)
|
48
|
+
|
49
|
+
connect_thread(latch)
|
49
50
|
|
50
|
-
|
51
|
+
return false unless latch.wait(CONNECT_TIMEOUT)
|
52
|
+
|
53
|
+
connected?
|
51
54
|
rescue StandardError => e
|
52
55
|
@config.logger.error("SSEClient start Error: #{e.inspect}")
|
56
|
+
connected?
|
53
57
|
end
|
54
58
|
|
55
59
|
def connected?
|
@@ -58,30 +62,27 @@ module SplitIoClient
|
|
58
62
|
|
59
63
|
private
|
60
64
|
|
61
|
-
def connect_thread
|
65
|
+
def connect_thread(latch)
|
62
66
|
@config.threads[:connect_stream] = Thread.new do
|
63
67
|
@config.logger.info('Starting connect_stream thread ...') if @config.debug_enabled
|
64
|
-
connect_stream
|
68
|
+
connect_stream(latch)
|
65
69
|
end
|
66
70
|
end
|
67
71
|
|
68
|
-
def connect_stream
|
69
|
-
interval = @back_off.interval
|
70
|
-
sleep(interval) if interval.positive?
|
71
|
-
|
72
|
+
def connect_stream(latch)
|
72
73
|
socket_write
|
73
74
|
|
74
|
-
while @
|
75
|
+
while connected? || @first_event.value
|
75
76
|
begin
|
76
77
|
partial_data = @socket.readpartial(10_000, timeout: @read_timeout)
|
77
78
|
|
79
|
+
read_first_event(partial_data, latch)
|
80
|
+
|
78
81
|
raise 'eof exception' if partial_data == :eof
|
79
82
|
rescue StandardError => e
|
80
|
-
@config.logger.error(e.inspect) if @config.debug_enabled
|
81
|
-
|
82
|
-
|
83
|
-
@socket = nil
|
84
|
-
connect_stream
|
83
|
+
@config.logger.error('Error reading partial data: ' + e.inspect) if @config.debug_enabled
|
84
|
+
close(Constants::PUSH_RETRYABLE_ERROR)
|
85
|
+
return
|
85
86
|
end
|
86
87
|
|
87
88
|
process_data(partial_data)
|
@@ -89,12 +90,31 @@ module SplitIoClient
|
|
89
90
|
end
|
90
91
|
|
91
92
|
def socket_write
|
93
|
+
@first_event.make_true
|
92
94
|
@socket = socket_connect
|
93
95
|
@socket.write(build_request(@uri))
|
94
|
-
dispatch_connected
|
95
96
|
rescue StandardError => e
|
96
97
|
@config.logger.error("Error during connecting to #{@uri.host}. Error: #{e.inspect}")
|
97
|
-
close
|
98
|
+
close(Constants::PUSH_NONRETRYABLE_ERROR)
|
99
|
+
end
|
100
|
+
|
101
|
+
def read_first_event(data, latch)
|
102
|
+
return unless @first_event.value
|
103
|
+
|
104
|
+
response_code = @event_parser.first_event(data)
|
105
|
+
@config.logger.debug("SSE client first event code: #{response_code}")
|
106
|
+
|
107
|
+
error_event = false
|
108
|
+
events = @event_parser.parse(data)
|
109
|
+
events.each { |e| error_event = true if e.event_type == ERROR_EVENT_TYPE }
|
110
|
+
@first_event.make_false
|
111
|
+
|
112
|
+
if response_code == OK_CODE && !error_event
|
113
|
+
@connected.make_true
|
114
|
+
dispatch_action(Constants::PUSH_CONNECTED)
|
115
|
+
end
|
116
|
+
|
117
|
+
latch.count_down
|
98
118
|
end
|
99
119
|
|
100
120
|
def socket_connect
|
@@ -104,11 +124,11 @@ module SplitIoClient
|
|
104
124
|
end
|
105
125
|
|
106
126
|
def process_data(partial_data)
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
127
|
+
return if partial_data.nil? || partial_data == KEEP_ALIVE_RESPONSE
|
128
|
+
|
129
|
+
@config.logger.debug("Event partial data: #{partial_data}") if @config.debug_enabled
|
130
|
+
events = @event_parser.parse(partial_data)
|
131
|
+
events.each { |event| process_event(event) }
|
112
132
|
rescue StandardError => e
|
113
133
|
@config.logger.error("process_data error: #{e.inspect}")
|
114
134
|
end
|
@@ -122,64 +142,32 @@ module SplitIoClient
|
|
122
142
|
req
|
123
143
|
end
|
124
144
|
|
125
|
-
def
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
def parse_event(buffer)
|
133
|
-
type = nil
|
134
|
-
|
135
|
-
buffer.each do |d|
|
136
|
-
splited_data = d.split(':')
|
137
|
-
|
138
|
-
case splited_data[0]
|
139
|
-
when 'event'
|
140
|
-
type = splited_data[1].strip
|
141
|
-
when 'data'
|
142
|
-
data = parse_event_data(d, type)
|
143
|
-
unless type.nil? || data[:data].nil?
|
144
|
-
event = StreamData.new(type, data[:client_id], data[:data], data[:channel])
|
145
|
-
dispatch_event(event)
|
146
|
-
end
|
147
|
-
end
|
145
|
+
def process_event(event)
|
146
|
+
case event.event_type
|
147
|
+
when ERROR_EVENT_TYPE
|
148
|
+
dispatch_error(event)
|
149
|
+
else
|
150
|
+
dispatch_event(event)
|
148
151
|
end
|
149
|
-
rescue StandardError => e
|
150
|
-
@config.logger.error("Error during parsing a event: #{e.inspect}")
|
151
152
|
end
|
152
153
|
|
153
|
-
def
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
{ client_id: client_id, channel: channel, data: parsed_data }
|
154
|
+
def dispatch_error(event)
|
155
|
+
@config.logger.error("Event error: #{event.event_type}, #{event.data}")
|
156
|
+
if event.data['code'] >= 40_140 && event.data['code'] <= 40_149
|
157
|
+
close(Constants::PUSH_RETRYABLE_ERROR)
|
158
|
+
elsif event.data['code'] >= 40_000 && event.data['code'] <= 49_999
|
159
|
+
close(Constants::PUSH_NONRETRYABLE_ERROR)
|
160
|
+
end
|
161
161
|
end
|
162
162
|
|
163
163
|
def dispatch_event(event)
|
164
|
-
raise SSEClientException.new(event), 'Error event' if event.event_type == 'error'
|
165
|
-
|
166
164
|
@config.logger.debug("Dispatching event: #{event.event_type}, #{event.channel}") if @config.debug_enabled
|
167
165
|
@on[:event].call(event)
|
168
|
-
rescue SSEClientException => e
|
169
|
-
@config.logger.error("Event error: #{e.event.event_type}, #{e.event.data}")
|
170
|
-
close
|
171
|
-
end
|
172
|
-
|
173
|
-
def dispatch_connected
|
174
|
-
@connected.make_true
|
175
|
-
@back_off.reset
|
176
|
-
@config.logger.debug('Dispatching connected') if @config.debug_enabled
|
177
|
-
@on[:connected].call
|
178
166
|
end
|
179
167
|
|
180
|
-
def
|
181
|
-
@config.logger.debug(
|
182
|
-
@on[:
|
168
|
+
def dispatch_action(action)
|
169
|
+
@config.logger.debug("Dispatching action: #{action}") if @config.debug_enabled
|
170
|
+
@on[:action].call(action)
|
183
171
|
end
|
184
172
|
end
|
185
173
|
end
|
@@ -0,0 +1,64 @@
|
|
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}")
|
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.debug("Error parsing first event: #{e.inspect}")
|
40
|
+
BAD_REQUEST_CODE
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def parse_event_data(data, type)
|
46
|
+
event_data = JSON.parse(data.sub('data: ', ''))
|
47
|
+
client_id = event_data['clientId']&.strip
|
48
|
+
channel = event_data['channel']&.strip
|
49
|
+
parsed_data = JSON.parse(event_data['data']) unless type == 'error'
|
50
|
+
parsed_data = event_data if type == 'error'
|
51
|
+
|
52
|
+
{ client_id: client_id, channel: channel, data: parsed_data }
|
53
|
+
end
|
54
|
+
|
55
|
+
def read_partial_data(data)
|
56
|
+
buffer = ''
|
57
|
+
buffer << data
|
58
|
+
buffer.chomp!
|
59
|
+
buffer.split("\n")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'concurrent
|
3
|
+
require 'concurrent'
|
4
4
|
|
5
5
|
module SplitIoClient
|
6
6
|
module SSE
|
@@ -8,7 +8,9 @@ module SplitIoClient
|
|
8
8
|
def initialize(config)
|
9
9
|
@config = config
|
10
10
|
@publisher_available = Concurrent::AtomicBoolean.new(true)
|
11
|
-
@
|
11
|
+
@publishers_pri = Concurrent::AtomicFixnum.new
|
12
|
+
@publishers_sec = Concurrent::AtomicFixnum.new
|
13
|
+
@on = { action: ->(_) {} }
|
12
14
|
|
13
15
|
yield self if block_given?
|
14
16
|
end
|
@@ -16,19 +18,15 @@ module SplitIoClient
|
|
16
18
|
def handle_incoming_occupancy_event(event)
|
17
19
|
if event.data['type'] == 'CONTROL'
|
18
20
|
process_event_control(event.data['controlType'])
|
19
|
-
|
20
|
-
process_event_occupancy(event.data['metrics']['publishers'])
|
21
|
+
else
|
22
|
+
process_event_occupancy(event.channel, event.data['metrics']['publishers'])
|
21
23
|
end
|
22
24
|
rescue StandardError => e
|
23
25
|
@config.logger.error(e)
|
24
26
|
end
|
25
27
|
|
26
|
-
def
|
27
|
-
@on[:
|
28
|
-
end
|
29
|
-
|
30
|
-
def on_push_shutdown(&action)
|
31
|
-
@on[:push_shutdown] = action
|
28
|
+
def on_action(&action)
|
29
|
+
@on[:action] = action
|
32
30
|
end
|
33
31
|
|
34
32
|
private
|
@@ -36,35 +34,42 @@ module SplitIoClient
|
|
36
34
|
def process_event_control(type)
|
37
35
|
case type
|
38
36
|
when 'STREAMING_PAUSED'
|
39
|
-
|
37
|
+
dispatch_action(Constants::PUSH_SUBSYSTEM_DOWN)
|
40
38
|
when 'STREAMING_RESUMED'
|
41
|
-
|
39
|
+
dispatch_action(Constants::PUSH_SUBSYSTEM_READY) if @publisher_available.value
|
42
40
|
when 'STREAMING_DISABLED'
|
43
|
-
|
41
|
+
dispatch_action(Constants::PUSH_SUBSYSTEM_OFF)
|
44
42
|
else
|
45
43
|
@config.logger.error("Incorrect event type: #{incoming_notification}")
|
46
44
|
end
|
47
45
|
end
|
48
46
|
|
49
|
-
def process_event_occupancy(publishers)
|
50
|
-
@config.logger.debug("
|
51
|
-
|
47
|
+
def process_event_occupancy(channel, publishers)
|
48
|
+
@config.logger.debug("Processed occupancy event with #{publishers} publishers. Channel: #{channel}")
|
49
|
+
|
50
|
+
update_publishers(channel, publishers)
|
51
|
+
|
52
|
+
if !are_publishers_available? && @publisher_available.value
|
52
53
|
@publisher_available.make_false
|
53
|
-
|
54
|
-
elsif
|
54
|
+
dispatch_action(Constants::PUSH_SUBSYSTEM_DOWN)
|
55
|
+
elsif are_publishers_available? && !@publisher_available.value
|
55
56
|
@publisher_available.make_true
|
56
|
-
|
57
|
+
dispatch_action(Constants::PUSH_SUBSYSTEM_READY)
|
57
58
|
end
|
58
59
|
end
|
59
60
|
|
60
|
-
def
|
61
|
-
@
|
62
|
-
@
|
61
|
+
def update_publishers(channel, publishers)
|
62
|
+
@publishers_pri.value = publishers if channel == Constants::CONTROL_PRI
|
63
|
+
@publishers_sec.value = publishers if channel == Constants::CONTROL_SEC
|
64
|
+
end
|
65
|
+
|
66
|
+
def are_publishers_available?
|
67
|
+
@publishers_pri.value.positive? || @publishers_sec.value.positive?
|
63
68
|
end
|
64
69
|
|
65
|
-
def
|
66
|
-
@config.logger.debug(
|
67
|
-
@on[:
|
70
|
+
def dispatch_action(action)
|
71
|
+
@config.logger.debug("Dispatching action: #{action}")
|
72
|
+
@on[:action].call(action)
|
68
73
|
end
|
69
74
|
end
|
70
75
|
end
|
@@ -13,11 +13,10 @@ module SplitIoClient
|
|
13
13
|
@notification_processor = SplitIoClient::SSE::NotificationProcessor.new(config, @splits_worker, @segments_worker)
|
14
14
|
@sse_client = SSE::EventSource::Client.new(@config) do |client|
|
15
15
|
client.on_event { |event| handle_incoming_message(event) }
|
16
|
-
client.
|
17
|
-
client.on_disconnect { process_disconnect }
|
16
|
+
client.on_action { |action| process_action(action) }
|
18
17
|
end
|
19
18
|
|
20
|
-
@on = {
|
19
|
+
@on = { action: ->(_) {} }
|
21
20
|
|
22
21
|
yield self if block_given?
|
23
22
|
end
|
@@ -48,22 +47,14 @@ module SplitIoClient
|
|
48
47
|
@segments_worker.stop
|
49
48
|
end
|
50
49
|
|
51
|
-
def
|
52
|
-
@on[:
|
53
|
-
end
|
54
|
-
|
55
|
-
def on_disconnect(&action)
|
56
|
-
@on[:disconnect] = action
|
57
|
-
end
|
58
|
-
|
59
|
-
def process_disconnect
|
60
|
-
@on[:disconnect].call
|
50
|
+
def on_action(&action)
|
51
|
+
@on[:action] = action
|
61
52
|
end
|
62
53
|
|
63
54
|
private
|
64
55
|
|
65
|
-
def
|
66
|
-
@on[:
|
56
|
+
def process_action(action)
|
57
|
+
@on[:action].call(action)
|
67
58
|
end
|
68
59
|
|
69
60
|
def handle_incoming_message(notification)
|
@@ -8,27 +8,39 @@ module SplitIoClient
|
|
8
8
|
@synchronizer = synchronizer
|
9
9
|
@config = config
|
10
10
|
@segments_repository = segments_repository
|
11
|
-
@queue = nil
|
12
|
-
end
|
13
|
-
|
14
|
-
def start
|
15
|
-
return if SplitIoClient::Helpers::ThreadHelper.alive?(:segment_update_worker, @config)
|
16
|
-
|
17
11
|
@queue = Queue.new
|
18
|
-
|
12
|
+
@running = Concurrent::AtomicBoolean.new(false)
|
19
13
|
end
|
20
14
|
|
21
15
|
def add_to_queue(change_number, segment_name)
|
22
|
-
|
16
|
+
unless @running.value
|
17
|
+
@config.logger.debug('segments worker not running.')
|
18
|
+
return
|
19
|
+
end
|
23
20
|
|
24
21
|
item = { change_number: change_number, segment_name: segment_name }
|
25
22
|
@config.logger.debug("SegmentsWorker add to queue #{item}")
|
26
23
|
@queue.push(item)
|
27
24
|
end
|
28
25
|
|
26
|
+
def start
|
27
|
+
if @running.value
|
28
|
+
@config.logger.debug('segments worker already running.')
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
@running.make_true
|
33
|
+
perform_thread
|
34
|
+
end
|
35
|
+
|
29
36
|
def stop
|
37
|
+
unless @running.value
|
38
|
+
@config.logger.debug('segments worker not running.')
|
39
|
+
return
|
40
|
+
end
|
41
|
+
|
42
|
+
@running.make_false
|
30
43
|
SplitIoClient::Helpers::ThreadHelper.stop(:segment_update_worker, @config)
|
31
|
-
@queue = nil
|
32
44
|
end
|
33
45
|
|
34
46
|
private
|
@@ -8,35 +8,53 @@ module SplitIoClient
|
|
8
8
|
@synchronizer = synchronizer
|
9
9
|
@config = config
|
10
10
|
@splits_repository = splits_repository
|
11
|
+
@queue = Queue.new
|
12
|
+
@running = Concurrent::AtomicBoolean.new(false)
|
11
13
|
end
|
12
14
|
|
13
15
|
def start
|
14
|
-
|
16
|
+
if @running.value
|
17
|
+
@config.logger.debug('splits worker already running.')
|
18
|
+
return
|
19
|
+
end
|
15
20
|
|
16
|
-
@
|
21
|
+
@running.make_true
|
17
22
|
perform_thread
|
18
23
|
end
|
19
24
|
|
25
|
+
def stop
|
26
|
+
unless @running.value
|
27
|
+
@config.logger.debug('splits worker not running.')
|
28
|
+
return
|
29
|
+
end
|
30
|
+
|
31
|
+
@running.make_false
|
32
|
+
SplitIoClient::Helpers::ThreadHelper.stop(:split_update_worker, @config)
|
33
|
+
end
|
34
|
+
|
20
35
|
def add_to_queue(change_number)
|
21
|
-
|
36
|
+
unless @running.value
|
37
|
+
@config.logger.debug('splits worker not running.')
|
38
|
+
return
|
39
|
+
end
|
22
40
|
|
23
41
|
@config.logger.debug("SplitsWorker add to queue #{change_number}")
|
24
42
|
@queue.push(change_number)
|
25
43
|
end
|
26
44
|
|
27
45
|
def kill_split(change_number, split_name, default_treatment)
|
28
|
-
|
46
|
+
unless @running.value
|
47
|
+
@config.logger.debug('splits worker not running.')
|
48
|
+
return
|
49
|
+
end
|
50
|
+
|
51
|
+
return if @splits_repository.get_change_number.to_i > change_number
|
29
52
|
|
30
53
|
@config.logger.debug("SplitsWorker kill #{split_name}, #{change_number}")
|
31
54
|
@splits_repository.kill(change_number, split_name, default_treatment)
|
32
55
|
add_to_queue(change_number)
|
33
56
|
end
|
34
57
|
|
35
|
-
def stop
|
36
|
-
SplitIoClient::Helpers::ThreadHelper.stop(:split_update_worker, @config)
|
37
|
-
@queue = nil
|
38
|
-
end
|
39
|
-
|
40
58
|
private
|
41
59
|
|
42
60
|
def perform
|
data/splitclient-rb.gemspec
CHANGED
@@ -55,7 +55,7 @@ Gem::Specification.new do |spec|
|
|
55
55
|
spec.add_runtime_dependency 'jwt', '>= 2.2.1'
|
56
56
|
spec.add_runtime_dependency 'lru_redux'
|
57
57
|
spec.add_runtime_dependency 'net-http-persistent', '>= 2.9'
|
58
|
-
spec.add_runtime_dependency 'redis', '>=
|
58
|
+
spec.add_runtime_dependency 'redis', '>= 4.2.2'
|
59
59
|
spec.add_runtime_dependency 'socketry', '~> 0.5.1'
|
60
60
|
spec.add_runtime_dependency 'thread_safe', '>= 0.3'
|
61
61
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: splitclient-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 7.2.
|
4
|
+
version: 7.2.3.pre.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Split Software
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: allocation_stats
|
@@ -268,14 +268,14 @@ dependencies:
|
|
268
268
|
requirements:
|
269
269
|
- - ">="
|
270
270
|
- !ruby/object:Gem::Version
|
271
|
-
version:
|
271
|
+
version: 4.2.2
|
272
272
|
type: :runtime
|
273
273
|
prerelease: false
|
274
274
|
version_requirements: !ruby/object:Gem::Requirement
|
275
275
|
requirements:
|
276
276
|
- - ">="
|
277
277
|
- !ruby/object:Gem::Version
|
278
|
-
version:
|
278
|
+
version: 4.2.2
|
279
279
|
- !ruby/object:Gem::Dependency
|
280
280
|
name: socketry
|
281
281
|
requirement: !ruby/object:Gem::Requirement
|
@@ -425,6 +425,7 @@ files:
|
|
425
425
|
- lib/splitclient-rb/split_logger.rb
|
426
426
|
- lib/splitclient-rb/sse/event_source/back_off.rb
|
427
427
|
- lib/splitclient-rb/sse/event_source/client.rb
|
428
|
+
- lib/splitclient-rb/sse/event_source/event_parser.rb
|
428
429
|
- lib/splitclient-rb/sse/event_source/event_types.rb
|
429
430
|
- lib/splitclient-rb/sse/event_source/stream_data.rb
|
430
431
|
- lib/splitclient-rb/sse/notification_manager_keeper.rb
|
@@ -454,9 +455,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
454
455
|
version: '0'
|
455
456
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
456
457
|
requirements:
|
457
|
-
- - "
|
458
|
+
- - ">"
|
458
459
|
- !ruby/object:Gem::Version
|
459
|
-
version:
|
460
|
+
version: 1.3.1
|
460
461
|
requirements: []
|
461
462
|
rubygems_version: 3.0.6
|
462
463
|
signing_key:
|