splitclient-rb 7.0.4.pre.rc3-java → 7.1.0-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +10 -3
  3. data/CHANGES.txt +3 -0
  4. data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +17 -6
  5. data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +21 -11
  6. data/lib/splitclient-rb/cache/repositories/splits_repository.rb +1 -1
  7. data/lib/splitclient-rb/clients/split_client.rb +4 -7
  8. data/lib/splitclient-rb/constants.rb +10 -0
  9. data/lib/splitclient-rb/engine/api/splits.rb +0 -1
  10. data/lib/splitclient-rb/engine/auth_api_client.rb +78 -0
  11. data/lib/splitclient-rb/engine/push_manager.rb +53 -0
  12. data/lib/splitclient-rb/engine/sync_manager.rb +123 -0
  13. data/lib/splitclient-rb/engine/synchronizer.rb +90 -0
  14. data/lib/splitclient-rb/exceptions.rb +8 -0
  15. data/lib/splitclient-rb/helpers/thread_helper.rb +25 -0
  16. data/lib/splitclient-rb/split_config.rb +42 -4
  17. data/lib/splitclient-rb/split_factory.rb +25 -15
  18. data/lib/splitclient-rb/sse/event_source/back_off.rb +25 -0
  19. data/lib/splitclient-rb/sse/event_source/client.rb +85 -60
  20. data/lib/splitclient-rb/sse/event_source/stream_data.rb +22 -0
  21. data/lib/splitclient-rb/sse/notification_manager_keeper.rb +71 -0
  22. data/lib/splitclient-rb/sse/notification_processor.rb +50 -0
  23. data/lib/splitclient-rb/sse/sse_handler.rb +47 -52
  24. data/lib/splitclient-rb/sse/workers/segments_worker.rb +21 -6
  25. data/lib/splitclient-rb/sse/workers/splits_worker.rb +24 -6
  26. data/lib/splitclient-rb/version.rb +1 -1
  27. data/lib/splitclient-rb.rb +10 -3
  28. data/splitclient-rb.gemspec +1 -0
  29. metadata +28 -7
  30. data/lib/splitclient-rb/engine/parser/split_adapter.rb +0 -105
  31. data/lib/splitclient-rb/sse/event_source/status.rb +0 -13
  32. data/lib/splitclient-rb/sse/workers/control_worker.rb +0 -34
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fd87f938e0d74bb4faf177feb49849972889e5d6
4
- data.tar.gz: 9688664e2908e4da58b18e0b7be14df1f195e423
3
+ metadata.gz: 17ccf70cef8223ac14aa96da12031c3205aa0f97
4
+ data.tar.gz: bd6561b5b2ffaec5d86054096ed8a39c4de0e9e3
5
5
  SHA512:
6
- metadata.gz: 7d332075f3806c6bcca59202d28bcaa6ed371ee91f4822dd3d5ea7aae189e1c486d9a0ffc3b5b41842705e91c744fb02fa617be26715e6b0b9795e9e31461cf7
7
- data.tar.gz: 0b97cedea81959f33eeb41f84a4ac9fcdfce37228a67e98a449f460e4d0b5cdec2b360cf8eb907df08f73de0b6c8fceb44d7f8873064152187e8be1ddd4a1056
6
+ metadata.gz: 630a6b415a70618e1fddafbcfac110c942249e69787c65418bf2d7f545b591809c235e002a68e682c37b27042af10d6d8cf920fe00858a79b22224c117ad7ae9
7
+ data.tar.gz: 86f6adf182a94bdd00e9459f3d46cae7d1943320851d0e9a2cc437e454bfb5259184af34b6f73861670888220299a8c4c0c8cebc7b33391155a2763d8c0182e9
data/.rubocop.yml CHANGED
@@ -2,18 +2,25 @@ Documentation:
2
2
  Enabled: false
3
3
 
4
4
  Metrics/AbcSize:
5
- Max: 25
5
+ Max: 26
6
6
 
7
7
  Metrics/MethodLength:
8
8
  Max: 20
9
9
 
10
10
  Metrics/ClassLength:
11
- Max: 120
11
+ Max: 150
12
12
 
13
13
  Metrics/LineLength:
14
- Max: 121
14
+ Max: 130
15
15
  Exclude:
16
16
  - spec/sse/**/*
17
+ - spec/integrations/**/*
18
+ - spec/engine/sync_manager_spec.rb
19
+
20
+ Style/BracesAroundHashParameters:
21
+ Exclude:
22
+ - spec/integrations/push_client_spec.rb
23
+ - spec/engine/synchronizer_spec.rb
17
24
 
18
25
  Metrics/BlockLength:
19
26
  Exclude:
data/CHANGES.txt CHANGED
@@ -1,3 +1,6 @@
1
+ 7.1.0 (Apr 30, 2020)
2
+ - Added support for the new Split streaming architecture. When enabled, the SDK will not poll for updates but instead receive notifications every time there's a change in your environments, allowing to process those much quicker. If disabled (default) or in the event of an issue, the SDK will fallback to the known polling mechanism to provide a seamless experience.
3
+
1
4
  7.0.3 (Jan 20, 2020)
2
5
  - Added integration tests.
3
6
  - Fixed impressions labels.
@@ -10,6 +10,7 @@ module SplitIoClient
10
10
  @metrics = metrics
11
11
  @config = config
12
12
  @sdk_blocker = sdk_blocker
13
+ @semaphore = Mutex.new
13
14
  end
14
15
 
15
16
  def call
@@ -27,24 +28,34 @@ module SplitIoClient
27
28
  end
28
29
 
29
30
  def fetch_segment(name)
30
- segments_api.fetch_segments_by_names([name])
31
+ @semaphore.synchronize do
32
+ segments_api.fetch_segments_by_names([name])
33
+ true
34
+ end
31
35
  rescue StandardError => error
32
36
  @config.log_found_exception(__method__.to_s, error)
37
+ false
33
38
  end
34
39
 
35
40
  def fetch_segments
36
- segments_api.fetch_segments_by_names(@segments_repository.used_segment_names)
41
+ @semaphore.synchronize do
42
+ segments_api.fetch_segments_by_names(@segments_repository.used_segment_names)
37
43
 
38
- @sdk_blocker.segments_ready!
44
+ @sdk_blocker.segments_ready!
45
+ end
39
46
  rescue StandardError => error
40
47
  @config.log_found_exception(__method__.to_s, error)
41
48
  end
42
49
 
50
+ def stop_segments_thread
51
+ SplitIoClient::Helpers::ThreadHelper.stop(:segment_fetcher, @config)
52
+ end
53
+
43
54
  private
44
55
 
45
56
  def segments_thread
46
57
  @config.threads[:segment_fetcher] = Thread.new do
47
- @config.logger.info('Starting segments fetcher service')
58
+ @config.logger.info('Starting segments fetcher service') if @config.debug_enabled
48
59
 
49
60
  loop do
50
61
  next unless @sdk_blocker.splits_repository.ready?
@@ -52,8 +63,8 @@ module SplitIoClient
52
63
  fetch_segments
53
64
  @config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
54
65
 
55
- sleep_for = StoreUtils.random_interval(@config.segments_refresh_rate)
56
- @config.logger.debug("Segments store is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
66
+ sleep_for = SplitIoClient::Cache::Stores::StoreUtils.random_interval(@config.segments_refresh_rate)
67
+ @config.logger.debug("Segments fetcher is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
57
68
  sleep(sleep_for)
58
69
  end
59
70
  end
@@ -10,6 +10,7 @@ module SplitIoClient
10
10
  @metrics = metrics
11
11
  @config = config
12
12
  @sdk_blocker = sdk_blocker
13
+ @semaphore = Mutex.new
13
14
  end
14
15
 
15
16
  def call
@@ -27,32 +28,41 @@ module SplitIoClient
27
28
  end
28
29
 
29
30
  def fetch_splits
30
- data = splits_since(@splits_repository.get_change_number)
31
+ @semaphore.synchronize do
32
+ data = splits_since(@splits_repository.get_change_number)
31
33
 
32
- data[:splits] && data[:splits].each do |split|
33
- add_split_unless_archived(split)
34
- end
35
-
36
- @splits_repository.set_segment_names(data[:segment_names])
37
- @splits_repository.set_change_number(data[:till])
34
+ data[:splits] && data[:splits].each do |split|
35
+ add_split_unless_archived(split)
36
+ end
38
37
 
39
- @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
38
+ @splits_repository.set_segment_names(data[:segment_names])
39
+ @splits_repository.set_change_number(data[:till])
40
40
 
41
- @sdk_blocker.splits_ready!
41
+ @config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
42
42
 
43
+ @sdk_blocker.splits_ready!
44
+ true
45
+ end
43
46
  rescue StandardError => error
44
47
  @config.log_found_exception(__method__.to_s, error)
48
+ false
49
+ end
50
+
51
+ def stop_splits_thread
52
+ SplitIoClient::Helpers::ThreadHelper.stop(:split_fetcher, @config)
45
53
  end
46
54
 
47
55
  private
48
56
 
49
57
  def splits_thread
50
58
  @config.threads[:split_fetcher] = Thread.new do
51
- @config.logger.info('Starting splits fetcher service')
59
+ @config.logger.info('Starting splits fetcher service') if @config.debug_enabled
52
60
  loop do
53
61
  fetch_splits
54
62
 
55
- sleep(StoreUtils.random_interval(@config.features_refresh_rate))
63
+ sleep_for = SplitIoClient::Cache::Stores::StoreUtils.random_interval(@config.features_refresh_rate)
64
+ @config.logger.debug("Splits fetcher is sleeping for: #{sleep_for} seconds") if @config.debug_enabled
65
+ sleep(sleep_for)
56
66
  end
57
67
  end
58
68
  end
@@ -133,7 +133,7 @@ module SplitIoClient
133
133
 
134
134
  return if split.nil?
135
135
 
136
- split[:label] = Engine::Models::Label::KILLED
136
+ split[:killed] = true
137
137
  split[:defaultTreatment] = default_treatment
138
138
  split[:changeNumber] = change_number
139
139
 
@@ -9,8 +9,9 @@ module SplitIoClient
9
9
  # @param api_key [String] the API key for your split account
10
10
  #
11
11
  # @return [SplitIoClient] split.io client instance
12
- def initialize(api_key, adapter = nil, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config, sse_handler)
12
+ def initialize(api_key, metrics, splits_repository, segments_repository, impressions_repository, metrics_repository, events_repository, sdk_blocker, config)
13
13
  @api_key = api_key
14
+ @metrics = metrics
14
15
  @splits_repository = splits_repository
15
16
  @segments_repository = segments_repository
16
17
  @impressions_repository = impressions_repository
@@ -19,8 +20,6 @@ module SplitIoClient
19
20
  @sdk_blocker = sdk_blocker
20
21
  @destroyed = false
21
22
  @config = config
22
- @adapter = adapter
23
- @sse_handler = sse_handler
24
23
  end
25
24
 
26
25
  def get_treatment(
@@ -62,8 +61,6 @@ module SplitIoClient
62
61
  thread.join
63
62
  end
64
63
 
65
- @sse_handler.sse_client.close if @config.push_notification_enabled
66
-
67
64
  @config.threads.values.each { |thread| Thread.kill(thread) }
68
65
 
69
66
  @splits_repository.clear
@@ -252,7 +249,7 @@ module SplitIoClient
252
249
  end
253
250
  latency = (Time.now - start) * 1000.0
254
251
  # Measure
255
- @adapter.metrics.time('sdk.' + calling_method, latency)
252
+ @metrics.time('sdk.' + calling_method, latency)
256
253
 
257
254
  treatments_for_impressions = get_treatment_for_impressions(treatments_labels_change_numbers)
258
255
 
@@ -336,7 +333,7 @@ module SplitIoClient
336
333
  store_impression(split_name, matching_key, bucketing_key, treatment_data, attributes) if store_impressions
337
334
 
338
335
  # Measure
339
- @adapter.metrics.time('sdk.' + calling_method, latency) unless multiple
336
+ @metrics.time('sdk.' + calling_method, latency) unless multiple
340
337
  rescue StandardError => error
341
338
  @config.log_found_exception(__method__.to_s, error)
342
339
 
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SplitIoClient::Constants
4
+ EXPIRATION_RATE = 600
5
+ CONTROL_PRI = 'control_pri'
6
+ CONTROL_SEC = 'control_sec'
7
+ OCCUPANCY_CHANNEL_PREFIX = '[?occupancy=metrics.publishers]'
8
+ FETCH_BACK_OFF_BASE_RETRIES = 1
9
+ end
10
+
@@ -16,7 +16,6 @@ module SplitIoClient
16
16
  start = Time.now
17
17
 
18
18
  response = get_api("#{@config.base_uri}/splitChanges", @api_key, since: since)
19
-
20
19
  if response.success?
21
20
  result = splits_with_segment_names(response.body)
22
21
 
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'cgi'
5
+
6
+ module SplitIoClient
7
+ module Engine
8
+ class AuthApiClient
9
+ def initialize(config)
10
+ @config = config
11
+ @api_client = SplitIoClient::Api::Client.new(@config)
12
+ end
13
+
14
+ def authenticate(api_key)
15
+ response = @api_client.get_api(@config.auth_service_url, api_key)
16
+
17
+ return process_success(response) if response.success?
18
+
19
+ if response.status >= 400 && response.status < 500
20
+ @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
21
+
22
+ return { push_enabled: false, retry: false }
23
+ end
24
+
25
+ @config.logger.debug("Error connecting to: #{@config.auth_service_url}. Response status: #{response.status}")
26
+ { push_enabled: false, retry: true }
27
+ end
28
+
29
+ private
30
+
31
+ def expiration(token_decoded)
32
+ exp = token_decoded[0]['exp']
33
+
34
+ (exp - SplitIoClient::Constants::EXPIRATION_RATE) - Time.now.getutc.to_i unless exp.nil?
35
+ rescue StandardError => e
36
+ @config.logger.error("Error getting token expiration: #{e.inspect}")
37
+ end
38
+
39
+ def channels(token_decoded)
40
+ capability = token_decoded[0]['x-ably-capability']
41
+ channels_hash = JSON.parse(capability)
42
+ channels_string = channels_hash.keys.join(',')
43
+ channels_string = control_channels(channels_string)
44
+ @config.logger.debug("Channels #{channels_string}") if @config.debug_enabled
45
+ CGI.escape(channels_string)
46
+ end
47
+
48
+ def decode_token(token)
49
+ JWT.decode token, nil, false
50
+ end
51
+
52
+ def process_success(response)
53
+ @config.logger.debug("Success connection to: #{@config.auth_service_url}") if @config.debug_enabled
54
+
55
+ body_json = JSON.parse(response.body, symbolize_names: true)
56
+ push_enabled = body_json[:pushEnabled]
57
+ token = body_json[:token]
58
+
59
+ if push_enabled
60
+ decoded_token = decode_token(token)
61
+ channels = channels(decoded_token)
62
+ exp = expiration(decoded_token)
63
+ end
64
+
65
+ { push_enabled: push_enabled, token: token, channels: channels, exp: exp, retry: false }
66
+ end
67
+
68
+ def control_channels(channels_string)
69
+ prefix = SplitIoClient::Constants::OCCUPANCY_CHANNEL_PREFIX
70
+ control_pri = SplitIoClient::Constants::CONTROL_PRI
71
+ control_sec = SplitIoClient::Constants::CONTROL_SEC
72
+ channels_string = channels_string.gsub(control_pri, "#{prefix}#{control_pri}")
73
+ channels_string = channels_string.gsub(control_sec, "#{prefix}#{control_sec}")
74
+ channels_string
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ class PushManager
6
+ def initialize(config, sse_handler, api_key)
7
+ @config = config
8
+ @sse_handler = sse_handler
9
+ @auth_api_client = AuthApiClient.new(@config)
10
+ @api_key = api_key
11
+ @back_off = SplitIoClient::SSE::EventSource::BackOff.new(@config.auth_retry_back_off_base, 1)
12
+ end
13
+
14
+ def start_sse
15
+ response = @auth_api_client.authenticate(@api_key)
16
+
17
+ @config.logger.debug("Auth service response push_enabled: #{response[:push_enabled]}") if @config.debug_enabled
18
+ if response[:push_enabled]
19
+ @sse_handler.start(response[:token], response[:channels])
20
+ schedule_next_token_refresh(response[:exp])
21
+ @back_off.reset
22
+ else
23
+ stop_sse
24
+ end
25
+
26
+ schedule_next_token_refresh(@back_off.interval) if response[:retry]
27
+ rescue StandardError => e
28
+ @config.logger.error("start_sse: #{e.inspect}")
29
+ end
30
+
31
+ def stop_sse
32
+ @sse_handler.process_disconnect if @sse_handler.sse_client.nil?
33
+ @sse_handler.stop
34
+ end
35
+
36
+ private
37
+
38
+ def schedule_next_token_refresh(time)
39
+ @config.threads[:schedule_next_token_refresh] = Thread.new do
40
+ begin
41
+ @config.logger.debug("schedule_next_token_refresh refresh in #{time} seconds.") if @config.debug_enabled
42
+ sleep(time)
43
+ @config.logger.debug('schedule_next_token_refresh starting ...') if @config.debug_enabled
44
+ stop_sse
45
+ start_sse
46
+ rescue StandardError => e
47
+ @config.logger.debug("schedule_next_token_refresh error: #{e.inspect}") if @config.debug_enabled
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SplitIoClient
4
+ module Engine
5
+ class SyncManager
6
+ include SplitIoClient::Cache::Fetchers
7
+
8
+ def initialize(
9
+ repositories,
10
+ api_key,
11
+ config,
12
+ sdk_blocker,
13
+ metrics
14
+ )
15
+ split_fetcher = SplitFetcher.new(repositories[:splits], api_key, metrics, config, sdk_blocker)
16
+ segment_fetcher = SegmentFetcher.new(repositories[:segments], api_key, metrics, config, sdk_blocker)
17
+ sync_params = { split_fetcher: split_fetcher, segment_fetcher: segment_fetcher }
18
+
19
+ @synchronizer = Synchronizer.new(repositories, api_key, config, sdk_blocker, sync_params)
20
+ notification_manager_keeper = SplitIoClient::SSE::NotificationManagerKeeper.new(config) do |manager|
21
+ manager.on_occupancy { |publisher_available| process_occupancy(publisher_available) }
22
+ manager.on_push_shutdown { process_push_shutdown }
23
+ end
24
+ @sse_handler = SplitIoClient::SSE::SSEHandler.new(
25
+ config,
26
+ @synchronizer,
27
+ repositories[:splits],
28
+ repositories[:segments],
29
+ notification_manager_keeper
30
+ ) do |handler|
31
+ handler.on_connected { process_connected }
32
+ handler.on_disconnect { process_disconnect }
33
+ end
34
+
35
+ @push_manager = PushManager.new(config, @sse_handler, api_key)
36
+ @config = config
37
+ end
38
+
39
+ def start
40
+ if @config.streaming_enabled
41
+ start_stream
42
+ start_stream_forked if defined?(PhusionPassenger)
43
+ elsif @config.standalone?
44
+ start_poll
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ # Starts tasks if stream is enabled.
51
+ def start_stream
52
+ @config.logger.debug('Starting push mode ...')
53
+ stream_start_thread
54
+ @synchronizer.start_periodic_data_recording
55
+
56
+ stream_start_sse_thread
57
+ end
58
+
59
+ def start_poll
60
+ @config.logger.debug('Starting polling mode ...')
61
+ @synchronizer.start_periodic_fetch
62
+ @synchronizer.start_periodic_data_recording
63
+ rescue StandardError => e
64
+ @config.logger.error("start_poll error : #{e.inspect}")
65
+ end
66
+
67
+ # Starts thread which fetch splits and segments once and trigger task to periodic data recording.
68
+ def stream_start_thread
69
+ @config.threads[:sync_manager_start_stream] = Thread.new do
70
+ begin
71
+ @synchronizer.sync_all
72
+ rescue StandardError => e
73
+ @config.logger.error("stream_start_thread error : #{e.inspect}")
74
+ end
75
+ end
76
+ end
77
+
78
+ # Starts thread which connect to sse and after that fetch splits and segments once.
79
+ def stream_start_sse_thread
80
+ @config.threads[:sync_manager_start_sse] = Thread.new do
81
+ begin
82
+ @push_manager.start_sse
83
+ rescue StandardError => e
84
+ @config.logger.error("stream_start_sse_thread error : #{e.inspect}")
85
+ end
86
+ end
87
+ end
88
+
89
+ def start_stream_forked
90
+ PhusionPassenger.on_event(:starting_worker_process) { |forked| start_stream if forked }
91
+ end
92
+
93
+ def process_connected
94
+ @synchronizer.stop_periodic_fetch
95
+ @synchronizer.sync_all
96
+ @sse_handler.start_workers
97
+ rescue StandardError => e
98
+ @config.logger.error("process_connected error: #{e.inspect}")
99
+ end
100
+
101
+ def process_disconnect
102
+ @sse_handler.stop_workers
103
+ @synchronizer.start_periodic_fetch
104
+ rescue StandardError => e
105
+ @config.logger.error("process_disconnect error: #{e.inspect}")
106
+ end
107
+
108
+ def process_occupancy(push_enable)
109
+ process_disconnect unless push_enable
110
+ process_connected if push_enable
111
+ rescue StandardError => e
112
+ @config.logger.error("process_occupancy error: #{e.inspect}")
113
+ end
114
+
115
+ def process_push_shutdown
116
+ @push_manager.stop_sse
117
+ process_disconnect
118
+ rescue StandardError => e
119
+ @config.logger.error("process_push_shutdown error: #{e.inspect}")
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,90 @@
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
+ def initialize(
10
+ repositories,
11
+ api_key,
12
+ config,
13
+ sdk_blocker,
14
+ params
15
+ )
16
+ @splits_repository = repositories[:splits]
17
+ @segments_repository = repositories[:segments]
18
+ @impressions_repository = repositories[:impressions]
19
+ @metrics_repository = repositories[:metrics]
20
+ @events_repository = repositories[:events]
21
+ @api_key = api_key
22
+ @config = config
23
+ @sdk_blocker = sdk_blocker
24
+ @split_fetcher = params[:split_fetcher]
25
+ @segment_fetcher = params[:segment_fetcher]
26
+ end
27
+
28
+ def sync_all
29
+ @config.logger.debug('Synchronizing Splits and Segments ...') if @config.debug_enabled
30
+ fetch_splits
31
+ fetch_segments
32
+ end
33
+
34
+ def start_periodic_data_recording
35
+ impressions_sender
36
+ metrics_sender
37
+ events_sender
38
+ end
39
+
40
+ def start_periodic_fetch
41
+ @split_fetcher.call
42
+ @segment_fetcher.call
43
+ end
44
+
45
+ def stop_periodic_fetch
46
+ @split_fetcher.stop_splits_thread
47
+ @segment_fetcher.stop_segments_thread
48
+ end
49
+
50
+ def fetch_splits
51
+ back_off = SplitIoClient::SSE::EventSource::BackOff.new(SplitIoClient::Constants::FETCH_BACK_OFF_BASE_RETRIES, 1)
52
+ loop do
53
+ break if @split_fetcher.fetch_splits
54
+
55
+ sleep(back_off.interval)
56
+ end
57
+ end
58
+
59
+ def fetch_segment(name)
60
+ back_off = SplitIoClient::SSE::EventSource::BackOff.new(SplitIoClient::Constants::FETCH_BACK_OFF_BASE_RETRIES, 1)
61
+ loop do
62
+ break if @segment_fetcher.fetch_segment(name)
63
+
64
+ sleep(back_off.interval)
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def fetch_segments
71
+ @segment_fetcher.fetch_segments
72
+ end
73
+
74
+ # Starts thread which loops constantly and sends impressions to the Split API
75
+ def impressions_sender
76
+ ImpressionsSender.new(@impressions_repository, @api_key, @config).call
77
+ end
78
+
79
+ # Starts thread which loops constantly and sends metrics to the Split API
80
+ def metrics_sender
81
+ MetricsSender.new(@metrics_repository, @api_key, @config).call
82
+ end
83
+
84
+ # Starts thread which loops constantly and sends events to the Split API
85
+ def events_sender
86
+ EventsSender.new(@events_repository, @config).call
87
+ end
88
+ end
89
+ end
90
+ end
@@ -4,4 +4,12 @@ module SplitIoClient
4
4
  class SDKShutdownException < SplitIoError; end
5
5
 
6
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
7
15
  end
@@ -0,0 +1,25 @@
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
+ sleep(0.1) while thread.status == 'run'
12
+ Thread.kill(thread)
13
+ end
14
+ rescue StandardError => e
15
+ config.logger.error(e.inspect)
16
+ end
17
+
18
+ def self.alive?(thread_sym, config)
19
+ thread = config.threads[thread_sym]
20
+
21
+ thread.nil? ? false : thread.alive?
22
+ end
23
+ end
24
+ end
25
+ end