splitclient-rb 7.3.0 → 7.3.2.pre.rc2
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/workflows/ci.yml +78 -0
- data/.rubocop.yml +2 -0
- data/CHANGES.txt +3 -0
- data/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +10 -13
- data/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +7 -10
- data/lib/splitclient-rb/cache/stores/localhost_split_store.rb +3 -6
- data/lib/splitclient-rb/clients/split_client.rb +5 -5
- data/lib/splitclient-rb/engine/api/segments.rb +8 -4
- data/lib/splitclient-rb/engine/api/splits.rb +5 -3
- data/lib/splitclient-rb/engine/back_off.rb +26 -0
- data/lib/splitclient-rb/engine/common/impressions_manager.rb +4 -4
- data/lib/splitclient-rb/engine/push_manager.rb +1 -1
- data/lib/splitclient-rb/engine/status_manager.rb +33 -0
- data/lib/splitclient-rb/engine/sync_manager.rb +24 -50
- data/lib/splitclient-rb/engine/synchronizer.rb +124 -14
- data/lib/splitclient-rb/managers/split_manager.rb +4 -4
- data/lib/splitclient-rb/split_config.rb +14 -0
- data/lib/splitclient-rb/split_factory.rb +18 -20
- data/lib/splitclient-rb/sse/event_source/client.rb +3 -2
- data/lib/splitclient-rb/sse/workers/segments_worker.rb +1 -5
- data/lib/splitclient-rb/sse/workers/splits_worker.rb +1 -8
- data/lib/splitclient-rb/version.rb +1 -1
- data/lib/splitclient-rb.rb +4 -4
- data/splitclient-rb.gemspec +3 -2
- metadata +27 -14
- data/.travis.yml +0 -20
- data/lib/splitclient-rb/cache/stores/sdk_blocker.rb +0 -64
- data/lib/splitclient-rb/sse/event_source/back_off.rb +0 -25
- data/sonar-scanner.sh +0 -42
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a5e2471231a284347d54b5a8adb871e2b9de38178cbd0061c73cf6c8dbf8b427
|
|
4
|
+
data.tar.gz: 47ac7b25ae20621d2a28ce4bad187be9eae90035ce0b0e53d9666da71950f0e2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92e5bb2e3bc64fec298790968d2f007a82819cb80838ed610f326db1dd95ac8b2618b0c98add8530f7bb7377d695e98a7581603040ec6ba04f17f45dcdb69a27
|
|
7
|
+
data.tar.gz: e1bf2c3b16fef2857f2556bb2e6997be2a11870f6c447b9cb069fa6c02b699a918f4d73cf2796101f3782ce6597f8bb0f871d8e78f0c56bf10c52a76055dd2ea
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
on:
|
|
2
|
+
push:
|
|
3
|
+
branches:
|
|
4
|
+
- master
|
|
5
|
+
pull_request:
|
|
6
|
+
branches:
|
|
7
|
+
- master
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
services:
|
|
13
|
+
redis:
|
|
14
|
+
image: redis
|
|
15
|
+
ports:
|
|
16
|
+
- 6379:6379
|
|
17
|
+
steps:
|
|
18
|
+
- name: Checkout code
|
|
19
|
+
uses: actions/checkout@v2
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
|
|
23
|
+
- name: Set up Ruby
|
|
24
|
+
uses: ruby/setup-ruby@477b21f02be01bcb8030d50f37cfec92bfa615b6
|
|
25
|
+
with:
|
|
26
|
+
ruby-version: 2.5
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: bundle install
|
|
30
|
+
|
|
31
|
+
- name: Run tests
|
|
32
|
+
run: bundle exec rake
|
|
33
|
+
|
|
34
|
+
- name: Fix code coverage paths
|
|
35
|
+
working-directory: ./coverage
|
|
36
|
+
run: |
|
|
37
|
+
sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' .resultset.json
|
|
38
|
+
ruby -rjson -e 'sqube = JSON.load(File.read(".resultset.json"))["RSpec"]["coverage"].transform_values {|lines| lines["lines"]}; total = { "RSpec" => { "coverage" => sqube, "timestamp" => Time.now.to_i }}; puts JSON.dump(total)' > .resultset.sonarqube.json
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
- name: SonarQube Scan (Push)
|
|
42
|
+
if: github.event_name == 'push'
|
|
43
|
+
uses: SonarSource/sonarcloud-github-action@v1.5
|
|
44
|
+
env:
|
|
45
|
+
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
|
|
46
|
+
with:
|
|
47
|
+
projectBaseDir: .
|
|
48
|
+
args: >
|
|
49
|
+
-Dsonar.host.url=${{ secrets.SONARQUBE_HOST }}
|
|
50
|
+
-Dsonar.projectName=${{ github.event.repository.name }}
|
|
51
|
+
-Dsonar.projectKey=${{ github.event.repository.name }}
|
|
52
|
+
-Dsonar.ruby.coverage.reportPaths=coverage/.resultset.sonarqube.json
|
|
53
|
+
-Dsonar.c.file.suffixes=-
|
|
54
|
+
-Dsonar.cpp.file.suffixes=-
|
|
55
|
+
-Dsonar.objc.file.suffixes=-
|
|
56
|
+
-Dsonar.links.ci="https://github.com/splitio/${{ github.event.repository.name }}/actions"
|
|
57
|
+
-Dsonar.links.scm="https://github.com/splitio/${{ github.event.repository.name }}"
|
|
58
|
+
|
|
59
|
+
- name: SonarQube Scan (Pull Request)
|
|
60
|
+
if: github.event_name == 'pull_request'
|
|
61
|
+
uses: SonarSource/sonarcloud-github-action@v1.5
|
|
62
|
+
env:
|
|
63
|
+
SONAR_TOKEN: ${{ secrets.SONARQUBE_TOKEN }}
|
|
64
|
+
with:
|
|
65
|
+
projectBaseDir: .
|
|
66
|
+
args: >
|
|
67
|
+
-Dsonar.host.url=${{ secrets.SONARQUBE_HOST }}
|
|
68
|
+
-Dsonar.projectName=${{ github.event.repository.name }}
|
|
69
|
+
-Dsonar.projectKey=${{ github.event.repository.name }}
|
|
70
|
+
-Dsonar.ruby.coverage.reportPaths=coverage/.resultset.sonarqube.json
|
|
71
|
+
-Dsonar.c.file.suffixes=-
|
|
72
|
+
-Dsonar.cpp.file.suffixes=-
|
|
73
|
+
-Dsonar.objc.file.suffixes=-
|
|
74
|
+
-Dsonar.links.ci="https://github.com/splitio/${{ github.event.repository.name }}/actions"
|
|
75
|
+
-Dsonar.links.scm="https://github.com/splitio/${{ github.event.repository.name }}"
|
|
76
|
+
-Dsonar.pullrequest.key=${{ github.event.pull_request.number }}
|
|
77
|
+
-Dsonar.pullrequest.branch=${{ github.event.pull_request.head.ref }}
|
|
78
|
+
-Dsonar.pullrequest.base=${{ github.event.pull_request.base.ref }}
|
data/.rubocop.yml
CHANGED
|
@@ -31,6 +31,7 @@ Metrics/LineLength:
|
|
|
31
31
|
- spec/engine/sync_manager_spec.rb
|
|
32
32
|
- spec/engine/auth_api_client_spec.rb
|
|
33
33
|
- spec/telemetry/synchronizer_spec.rb
|
|
34
|
+
- spec/splitclient/split_config_spec.rb
|
|
34
35
|
|
|
35
36
|
Style/BracesAroundHashParameters:
|
|
36
37
|
Exclude:
|
|
@@ -62,3 +63,4 @@ AllCops:
|
|
|
62
63
|
- lib/splitclient-rb/engine/models/**/*
|
|
63
64
|
- lib/splitclient-rb/engine/parser/**/*
|
|
64
65
|
- spec/telemetry/synchronizer_spec.rb
|
|
66
|
+
- lib/splitclient-rb/engine/synchronizer.rb
|
data/CHANGES.txt
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
CHANGES
|
|
2
2
|
|
|
3
|
+
7.3.1 (Jul 26, 2021)
|
|
4
|
+
- Updated the synchronization flow to be more reliable in the event of an edge case generating delay in cache purge propagation, keeping the SDK cache properly synced.
|
|
5
|
+
|
|
3
6
|
7.3.0 (Jul 12, 2021)
|
|
4
7
|
- Updated SDK telemetry storage, metrics and updater to be more effective and send less often.
|
|
5
8
|
- Fixed high cpu usage when api key is wrong.
|
|
@@ -4,11 +4,10 @@ module SplitIoClient
|
|
|
4
4
|
class SegmentFetcher
|
|
5
5
|
attr_reader :segments_repository
|
|
6
6
|
|
|
7
|
-
def initialize(segments_repository, api_key, config,
|
|
7
|
+
def initialize(segments_repository, api_key, config, telemetry_runtime_producer)
|
|
8
8
|
@segments_repository = segments_repository
|
|
9
9
|
@api_key = api_key
|
|
10
10
|
@config = config
|
|
11
|
-
@sdk_blocker = sdk_blocker
|
|
12
11
|
@semaphore = Mutex.new
|
|
13
12
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
14
13
|
end
|
|
@@ -30,16 +29,19 @@ module SplitIoClient
|
|
|
30
29
|
def fetch_segments_if_not_exists(names, cache_control_headers = false)
|
|
31
30
|
names.each do |name|
|
|
32
31
|
change_number = @segments_repository.get_change_number(name)
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
|
|
33
|
+
if change_number == -1
|
|
34
|
+
fetch_options = { cache_control_headers: cache_control_headers, till: nil }
|
|
35
|
+
fetch_segment(name, fetch_options) if change_number == -1
|
|
36
|
+
end
|
|
35
37
|
end
|
|
36
38
|
rescue StandardError => error
|
|
37
39
|
@config.log_found_exception(__method__.to_s, error)
|
|
38
40
|
end
|
|
39
41
|
|
|
40
|
-
def fetch_segment(name,
|
|
42
|
+
def fetch_segment(name, fetch_options = { cache_control_headers: false, till: nil })
|
|
41
43
|
@semaphore.synchronize do
|
|
42
|
-
segments_api.fetch_segments_by_names([name],
|
|
44
|
+
segments_api.fetch_segments_by_names([name], fetch_options)
|
|
43
45
|
end
|
|
44
46
|
rescue StandardError => error
|
|
45
47
|
@config.log_found_exception(__method__.to_s, error)
|
|
@@ -49,11 +51,11 @@ module SplitIoClient
|
|
|
49
51
|
@semaphore.synchronize do
|
|
50
52
|
segments_api.fetch_segments_by_names(@segments_repository.used_segment_names)
|
|
51
53
|
|
|
52
|
-
|
|
53
|
-
@sdk_blocker.sdk_internal_ready
|
|
54
|
+
true
|
|
54
55
|
end
|
|
55
56
|
rescue StandardError => error
|
|
56
57
|
@config.log_found_exception(__method__.to_s, error)
|
|
58
|
+
false
|
|
57
59
|
end
|
|
58
60
|
|
|
59
61
|
def stop_segments_thread
|
|
@@ -67,11 +69,6 @@ module SplitIoClient
|
|
|
67
69
|
@config.logger.info('Starting segments fetcher service') if @config.debug_enabled
|
|
68
70
|
|
|
69
71
|
loop do
|
|
70
|
-
unless @sdk_blocker.splits_repository.ready?
|
|
71
|
-
sleep 0.2
|
|
72
|
-
next
|
|
73
|
-
end
|
|
74
|
-
|
|
75
72
|
fetch_segments
|
|
76
73
|
@config.logger.debug("Segment names: #{@segments_repository.used_segment_names.to_a}") if @config.debug_enabled
|
|
77
74
|
|
|
@@ -4,11 +4,10 @@ module SplitIoClient
|
|
|
4
4
|
class SplitFetcher
|
|
5
5
|
attr_reader :splits_repository
|
|
6
6
|
|
|
7
|
-
def initialize(splits_repository, api_key, config,
|
|
7
|
+
def initialize(splits_repository, api_key, config, telemetry_runtime_producer)
|
|
8
8
|
@splits_repository = splits_repository
|
|
9
9
|
@api_key = api_key
|
|
10
10
|
@config = config
|
|
11
|
-
@sdk_blocker = sdk_blocker
|
|
12
11
|
@semaphore = Mutex.new
|
|
13
12
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
14
13
|
end
|
|
@@ -27,9 +26,9 @@ module SplitIoClient
|
|
|
27
26
|
end
|
|
28
27
|
end
|
|
29
28
|
|
|
30
|
-
def fetch_splits(
|
|
29
|
+
def fetch_splits(fetch_options = { cache_control_headers: false, till: nil })
|
|
31
30
|
@semaphore.synchronize do
|
|
32
|
-
data = splits_since(@splits_repository.get_change_number,
|
|
31
|
+
data = splits_since(@splits_repository.get_change_number, fetch_options)
|
|
33
32
|
|
|
34
33
|
data[:splits] && data[:splits].each do |split|
|
|
35
34
|
add_split_unless_archived(split)
|
|
@@ -40,13 +39,11 @@ module SplitIoClient
|
|
|
40
39
|
|
|
41
40
|
@config.logger.debug("segments seen(#{data[:segment_names].length}): #{data[:segment_names].to_a}") if @config.debug_enabled
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
data[:segment_names]
|
|
42
|
+
{ segment_names: data[:segment_names], success: true }
|
|
46
43
|
end
|
|
47
44
|
rescue StandardError => error
|
|
48
45
|
@config.log_found_exception(__method__.to_s, error)
|
|
49
|
-
[]
|
|
46
|
+
{ segment_names: [], success: false }
|
|
50
47
|
end
|
|
51
48
|
|
|
52
49
|
def stop_splits_thread
|
|
@@ -68,8 +65,8 @@ module SplitIoClient
|
|
|
68
65
|
end
|
|
69
66
|
end
|
|
70
67
|
|
|
71
|
-
def splits_since(since,
|
|
72
|
-
splits_api.since(since,
|
|
68
|
+
def splits_since(since, fetch_options = { cache_control_headers: false, till: nil })
|
|
69
|
+
splits_api.since(since, fetch_options)
|
|
73
70
|
end
|
|
74
71
|
|
|
75
72
|
def add_split_unless_archived(split)
|
|
@@ -7,10 +7,10 @@ module SplitIoClient
|
|
|
7
7
|
require 'yaml'
|
|
8
8
|
attr_reader :splits_repository
|
|
9
9
|
|
|
10
|
-
def initialize(splits_repository, config,
|
|
10
|
+
def initialize(splits_repository, config, status_manager = nil)
|
|
11
11
|
@splits_repository = splits_repository
|
|
12
12
|
@config = config
|
|
13
|
-
@
|
|
13
|
+
@status_manager = status_manager
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def call
|
|
@@ -45,10 +45,7 @@ module SplitIoClient
|
|
|
45
45
|
store_split(split)
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
if @
|
|
49
|
-
@sdk_blocker.splits_ready!
|
|
50
|
-
@sdk_blocker.segments_ready!
|
|
51
|
-
end
|
|
48
|
+
@status_manager.ready! if @status_manager
|
|
52
49
|
rescue StandardError => error
|
|
53
50
|
@config.logger.error('Error while parsing the split file. ' \
|
|
54
51
|
'Check that the input file matches the expected format')
|
|
@@ -14,13 +14,13 @@ module SplitIoClient
|
|
|
14
14
|
# @param api_key [String] the API key for your split account
|
|
15
15
|
#
|
|
16
16
|
# @return [SplitIoClient] split.io client instance
|
|
17
|
-
def initialize(api_key, repositories,
|
|
17
|
+
def initialize(api_key, repositories, status_manager, config, impressions_manager, telemetry_evaluation_producer)
|
|
18
18
|
@api_key = api_key
|
|
19
19
|
@splits_repository = repositories[:splits]
|
|
20
20
|
@segments_repository = repositories[:segments]
|
|
21
21
|
@impressions_repository = repositories[:impressions]
|
|
22
22
|
@events_repository = repositories[:events]
|
|
23
|
-
@
|
|
23
|
+
@status_manager = status_manager
|
|
24
24
|
@destroyed = false
|
|
25
25
|
@config = config
|
|
26
26
|
@impressions_manager = impressions_manager
|
|
@@ -137,7 +137,7 @@ module SplitIoClient
|
|
|
137
137
|
else
|
|
138
138
|
{
|
|
139
139
|
treatment: treatment_data[:treatment],
|
|
140
|
-
config: treatment_data[:config]
|
|
140
|
+
config: treatment_data[:config],
|
|
141
141
|
}
|
|
142
142
|
end
|
|
143
143
|
end
|
|
@@ -157,7 +157,7 @@ module SplitIoClient
|
|
|
157
157
|
end
|
|
158
158
|
|
|
159
159
|
def block_until_ready(time = nil)
|
|
160
|
-
@
|
|
160
|
+
@status_manager.wait_until_ready(time) if @status_manager
|
|
161
161
|
end
|
|
162
162
|
|
|
163
163
|
private
|
|
@@ -310,7 +310,7 @@ module SplitIoClient
|
|
|
310
310
|
end
|
|
311
311
|
|
|
312
312
|
def ready?
|
|
313
|
-
return @
|
|
313
|
+
return @status_manager.ready? if @status_manager
|
|
314
314
|
true
|
|
315
315
|
end
|
|
316
316
|
|
|
@@ -11,13 +11,14 @@ module SplitIoClient
|
|
|
11
11
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def fetch_segments_by_names(names,
|
|
14
|
+
def fetch_segments_by_names(names, fetch_options = { cache_control_headers: false, till: nil })
|
|
15
15
|
return if names.nil? || names.empty?
|
|
16
16
|
|
|
17
17
|
names.each do |name|
|
|
18
18
|
since = @segments_repository.get_change_number(name)
|
|
19
|
+
|
|
19
20
|
loop do
|
|
20
|
-
segment = fetch_segment_changes(name, since,
|
|
21
|
+
segment = fetch_segment_changes(name, since, fetch_options)
|
|
21
22
|
@segments_repository.add_to_segment(segment)
|
|
22
23
|
|
|
23
24
|
@config.split_logger.log_if_debug("Segment #{name} fetched before: #{since}, \
|
|
@@ -32,9 +33,12 @@ module SplitIoClient
|
|
|
32
33
|
|
|
33
34
|
private
|
|
34
35
|
|
|
35
|
-
def fetch_segment_changes(name, since,
|
|
36
|
+
def fetch_segment_changes(name, since, fetch_options = { cache_control_headers: false, till: nil })
|
|
36
37
|
start = Time.now
|
|
37
|
-
|
|
38
|
+
|
|
39
|
+
params = { since: since }
|
|
40
|
+
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
|
|
41
|
+
response = get_api("#{@config.base_uri}/segmentChanges/#{name}", @api_key, params, fetch_options[:cache_control_headers])
|
|
38
42
|
|
|
39
43
|
if response.success?
|
|
40
44
|
segment = JSON.parse(response.body, symbolize_names: true)
|
|
@@ -10,10 +10,12 @@ module SplitIoClient
|
|
|
10
10
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
11
11
|
end
|
|
12
12
|
|
|
13
|
-
def since(since,
|
|
13
|
+
def since(since, fetch_options = { cache_control_headers: false, till: nil })
|
|
14
14
|
start = Time.now
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
params = { since: since }
|
|
17
|
+
params[:till] = fetch_options[:till] unless fetch_options[:till].nil?
|
|
18
|
+
response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers])
|
|
17
19
|
if response.success?
|
|
18
20
|
result = splits_with_segment_names(response.body)
|
|
19
21
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: false
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Engine
|
|
5
|
+
BACKOFF_MAX_ALLOWED = 1.8
|
|
6
|
+
class BackOff
|
|
7
|
+
def initialize(back_off_base, attempt = 0, max_allowed = BACKOFF_MAX_ALLOWED)
|
|
8
|
+
@attempt = attempt
|
|
9
|
+
@back_off_base = back_off_base
|
|
10
|
+
@max_allowed = max_allowed
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def interval
|
|
14
|
+
interval = 0
|
|
15
|
+
interval = (@back_off_base * (2**@attempt)) if @attempt.positive?
|
|
16
|
+
@attempt += 1
|
|
17
|
+
|
|
18
|
+
interval >= @max_allowed ? @max_allowed : interval
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def reset
|
|
22
|
+
@attempt = 0
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -21,8 +21,8 @@ module SplitIoClient
|
|
|
21
21
|
@impression_counter.inc(split_name, impression_data[:m]) if optimized? && !redis?
|
|
22
22
|
|
|
23
23
|
impression(impression_data, params[:attributes])
|
|
24
|
-
rescue StandardError =>
|
|
25
|
-
@config.log_found_exception(__method__.to_s,
|
|
24
|
+
rescue StandardError => e
|
|
25
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
def track(impressions)
|
|
@@ -48,8 +48,8 @@ module SplitIoClient
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
record_stats(queued, dropped, dedupe)
|
|
51
|
-
rescue StandardError =>
|
|
52
|
-
@config.log_found_exception(__method__.to_s,
|
|
51
|
+
rescue StandardError => e
|
|
52
|
+
@config.log_found_exception(__method__.to_s, e)
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
private
|
|
@@ -8,7 +8,7 @@ module SplitIoClient
|
|
|
8
8
|
@sse_handler = sse_handler
|
|
9
9
|
@auth_api_client = AuthApiClient.new(@config, telemetry_runtime_producer)
|
|
10
10
|
@api_key = api_key
|
|
11
|
-
@back_off =
|
|
11
|
+
@back_off = Engine::BackOff.new(@config.auth_retry_back_off_base, 1)
|
|
12
12
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
13
13
|
end
|
|
14
14
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module SplitIoClient
|
|
4
|
+
module Engine
|
|
5
|
+
class StatusManager
|
|
6
|
+
def initialize(config)
|
|
7
|
+
@config = config
|
|
8
|
+
@sdk_ready = Concurrent::CountDownLatch.new(1)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ready?
|
|
12
|
+
return true if @config.consumer?
|
|
13
|
+
|
|
14
|
+
@sdk_ready.wait(0)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def ready!
|
|
18
|
+
return if ready?
|
|
19
|
+
|
|
20
|
+
@sdk_ready.count_down
|
|
21
|
+
@config.logger.info('SplitIO SDK is ready')
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def wait_until_ready(seconds = nil)
|
|
25
|
+
return if @config.consumer?
|
|
26
|
+
|
|
27
|
+
timeout = seconds || @config.block_until_ready
|
|
28
|
+
|
|
29
|
+
raise SDKBlockerTimeoutExpiredException, 'SDK start up timeout expired' unless @sdk_ready.wait(timeout)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -12,8 +12,8 @@ module SplitIoClient
|
|
|
12
12
|
config,
|
|
13
13
|
synchronizer,
|
|
14
14
|
telemetry_runtime_producer,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
telemetry_synchronizer,
|
|
16
|
+
status_manager
|
|
17
17
|
)
|
|
18
18
|
@synchronizer = synchronizer
|
|
19
19
|
notification_manager_keeper = SSE::NotificationManagerKeeper.new(config, telemetry_runtime_producer) do |manager|
|
|
@@ -33,56 +33,37 @@ module SplitIoClient
|
|
|
33
33
|
@sse_connected = Concurrent::AtomicBoolean.new(false)
|
|
34
34
|
@config = config
|
|
35
35
|
@telemetry_runtime_producer = telemetry_runtime_producer
|
|
36
|
-
@sdk_blocker = sdk_blocker
|
|
37
36
|
@telemetry_synchronizer = telemetry_synchronizer
|
|
37
|
+
@status_manager = status_manager
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
def start
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
start_stream_forked if defined?(PhusionPassenger)
|
|
44
|
-
elsif @config.standalone?
|
|
45
|
-
start_poll
|
|
46
|
-
end
|
|
41
|
+
@config.threads[:start_sdk] = Thread.new do
|
|
42
|
+
sleep(0.5) until @synchronizer.sync_all(false)
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
@status_manager.ready!
|
|
45
|
+
@telemetry_synchronizer.synchronize_config
|
|
46
|
+
@synchronizer.start_periodic_data_recording
|
|
47
|
+
connected = false
|
|
50
48
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def start_stream
|
|
55
|
-
@config.logger.debug('Starting push mode ...')
|
|
56
|
-
@synchronizer.sync_all
|
|
57
|
-
@synchronizer.start_periodic_data_recording
|
|
58
|
-
|
|
59
|
-
start_sse_connection_thread
|
|
60
|
-
end
|
|
49
|
+
if @config.streaming_enabled
|
|
50
|
+
@config.logger.debug('Starting Straming mode ...')
|
|
51
|
+
connected = @push_manager.start_sse
|
|
61
52
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING)
|
|
67
|
-
rescue StandardError => e
|
|
68
|
-
@config.logger.error("start_poll error : #{e.inspect}")
|
|
69
|
-
end
|
|
53
|
+
if defined?(PhusionPassenger)
|
|
54
|
+
PhusionPassenger.on_event(:starting_worker_process) { |forked| sse_thread_forked if forked }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
70
57
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
connected = @push_manager.start_sse
|
|
76
|
-
@synchronizer.start_periodic_fetch unless connected
|
|
77
|
-
rescue StandardError => e
|
|
78
|
-
@config.logger.error("start_sse_connection_thread error : #{e.inspect}")
|
|
58
|
+
unless connected
|
|
59
|
+
@config.logger.debug('Starting polling mode ...')
|
|
60
|
+
@synchronizer.start_periodic_fetch
|
|
61
|
+
record_telemetry(Telemetry::Domain::Constants::SYNC_MODE, SYNC_MODE_POLLING)
|
|
79
62
|
end
|
|
80
63
|
end
|
|
81
64
|
end
|
|
82
65
|
|
|
83
|
-
|
|
84
|
-
PhusionPassenger.on_event(:starting_worker_process) { |forked| start_stream if forked }
|
|
85
|
-
end
|
|
66
|
+
private
|
|
86
67
|
|
|
87
68
|
def process_action(action)
|
|
88
69
|
case action
|
|
@@ -165,16 +146,9 @@ module SplitIoClient
|
|
|
165
146
|
@telemetry_runtime_producer.record_streaming_event(type, data)
|
|
166
147
|
end
|
|
167
148
|
|
|
168
|
-
def
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
@sdk_blocker.wait_unitil_internal_ready unless @config.consumer?
|
|
172
|
-
@telemetry_synchronizer.synchronize_config
|
|
173
|
-
rescue SplitIoClient::SDKShutdownException
|
|
174
|
-
@telemetry_synchronizer.synchronize_config
|
|
175
|
-
@config.logger.info('Posting Telemetry config due to shutdown')
|
|
176
|
-
end
|
|
177
|
-
end
|
|
149
|
+
def sse_thread_forked
|
|
150
|
+
connected = @push_manager.start_sse
|
|
151
|
+
@synchronizer.start_periodic_fetch unless connected
|
|
178
152
|
end
|
|
179
153
|
end
|
|
180
154
|
end
|
|
@@ -6,13 +6,14 @@ module SplitIoClient
|
|
|
6
6
|
include SplitIoClient::Cache::Fetchers
|
|
7
7
|
include SplitIoClient::Cache::Senders
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS = 10
|
|
10
|
+
ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS = 60
|
|
11
|
+
ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES = 10
|
|
10
12
|
|
|
11
13
|
def initialize(
|
|
12
14
|
repositories,
|
|
13
15
|
api_key,
|
|
14
16
|
config,
|
|
15
|
-
sdk_blocker,
|
|
16
17
|
params
|
|
17
18
|
)
|
|
18
19
|
@splits_repository = repositories[:splits]
|
|
@@ -21,7 +22,6 @@ module SplitIoClient
|
|
|
21
22
|
@events_repository = repositories[:events]
|
|
22
23
|
@api_key = api_key
|
|
23
24
|
@config = config
|
|
24
|
-
@sdk_blocker = sdk_blocker
|
|
25
25
|
@split_fetcher = params[:split_fetcher]
|
|
26
26
|
@segment_fetcher = params[:segment_fetcher]
|
|
27
27
|
@impressions_api = SplitIoClient::Api::Impressions.new(@api_key, @config, params[:telemetry_runtime_producer])
|
|
@@ -29,12 +29,16 @@ module SplitIoClient
|
|
|
29
29
|
@telemetry_synchronizer = params[:telemetry_synchronizer]
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def sync_all
|
|
32
|
+
def sync_all(asynchronous = true)
|
|
33
|
+
unless asynchronous
|
|
34
|
+
return sync_splits_and_segments
|
|
35
|
+
end
|
|
36
|
+
|
|
33
37
|
@config.threads[:sync_all_thread] = Thread.new do
|
|
34
|
-
|
|
35
|
-
@split_fetcher.fetch_splits
|
|
36
|
-
@segment_fetcher.fetch_segments
|
|
38
|
+
sync_splits_and_segments
|
|
37
39
|
end
|
|
40
|
+
|
|
41
|
+
true
|
|
38
42
|
end
|
|
39
43
|
|
|
40
44
|
def start_periodic_data_recording
|
|
@@ -54,19 +58,114 @@ module SplitIoClient
|
|
|
54
58
|
@segment_fetcher.stop_segments_thread
|
|
55
59
|
end
|
|
56
60
|
|
|
57
|
-
def fetch_splits
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
def fetch_splits(target_change_number)
|
|
62
|
+
return if target_change_number <= @splits_repository.get_change_number.to_i
|
|
63
|
+
|
|
64
|
+
fetch_options = { cache_control_headers: true, till: nil }
|
|
65
|
+
|
|
66
|
+
result = attempt_splits_sync(target_change_number,
|
|
67
|
+
fetch_options,
|
|
68
|
+
@config.on_demand_fetch_max_retries,
|
|
69
|
+
@config.on_demand_fetch_retry_delay_seconds,
|
|
70
|
+
false)
|
|
71
|
+
|
|
72
|
+
attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
|
|
73
|
+
if result[:success]
|
|
74
|
+
@segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty?
|
|
75
|
+
@config.logger.debug("Refresh completed in #{attempts} attempts.") if @config.debug_enabled
|
|
76
|
+
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
fetch_options[:till] = target_change_number
|
|
81
|
+
result = attempt_splits_sync(target_change_number,
|
|
82
|
+
fetch_options,
|
|
83
|
+
ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES,
|
|
84
|
+
nil,
|
|
85
|
+
true)
|
|
86
|
+
|
|
87
|
+
attempts = ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES - result[:remaining_attempts]
|
|
88
|
+
|
|
89
|
+
if result[:success]
|
|
90
|
+
@segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty?
|
|
91
|
+
@config.logger.debug("Refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled
|
|
92
|
+
else
|
|
93
|
+
@config.logger.debug("No changes fetched after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled
|
|
94
|
+
end
|
|
95
|
+
rescue StandardError => error
|
|
96
|
+
@config.log_found_exception(__method__.to_s, error)
|
|
60
97
|
end
|
|
61
98
|
|
|
62
|
-
def fetch_segment(name)
|
|
63
|
-
@
|
|
99
|
+
def fetch_segment(name, target_change_number)
|
|
100
|
+
return if target_change_number <= @segments_repository.get_change_number(name).to_i
|
|
101
|
+
|
|
102
|
+
fetch_options = { cache_control_headers: true, till: nil }
|
|
103
|
+
result = attempt_segment_sync(name,
|
|
104
|
+
target_change_number,
|
|
105
|
+
fetch_options,
|
|
106
|
+
@config.on_demand_fetch_max_retries,
|
|
107
|
+
@config.on_demand_fetch_retry_delay_seconds,
|
|
108
|
+
false)
|
|
109
|
+
|
|
110
|
+
attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
|
|
111
|
+
if result[:success]
|
|
112
|
+
@config.logger.debug("Segment #{name} refresh completed in #{attempts} attempts.") if @config.debug_enabled
|
|
113
|
+
|
|
114
|
+
return
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
fetch_options = { cache_control_headers: true, till: target_change_number }
|
|
118
|
+
result = attempt_segment_sync(name,
|
|
119
|
+
target_change_number,
|
|
120
|
+
fetch_options,
|
|
121
|
+
ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES,
|
|
122
|
+
nil,
|
|
123
|
+
true)
|
|
124
|
+
|
|
125
|
+
attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
|
|
126
|
+
if result[:success]
|
|
127
|
+
@config.logger.debug("Segment #{name} refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled
|
|
128
|
+
else
|
|
129
|
+
@config.logger.debug("No changes fetched for segment #{name} after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled
|
|
130
|
+
end
|
|
131
|
+
rescue StandardError => error
|
|
132
|
+
@config.log_found_exception(__method__.to_s, error)
|
|
64
133
|
end
|
|
65
134
|
|
|
66
135
|
private
|
|
67
136
|
|
|
68
|
-
def
|
|
69
|
-
|
|
137
|
+
def attempt_segment_sync(name, target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff)
|
|
138
|
+
remaining_attempts = max_retries
|
|
139
|
+
backoff = Engine::BackOff.new(ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS, 0, ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS) if with_backoff
|
|
140
|
+
|
|
141
|
+
loop do
|
|
142
|
+
remaining_attempts -= 1
|
|
143
|
+
|
|
144
|
+
@segment_fetcher.fetch_segment(name, fetch_options)
|
|
145
|
+
|
|
146
|
+
return sync_result(true, remaining_attempts) if target_cn <= @segments_repository.get_change_number(name).to_i
|
|
147
|
+
return sync_result(false, remaining_attempts) if remaining_attempts <= 0
|
|
148
|
+
|
|
149
|
+
delay = with_backoff ? backoff.interval : retry_delay_seconds
|
|
150
|
+
sleep(delay)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def attempt_splits_sync(target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff)
|
|
155
|
+
remaining_attempts = max_retries
|
|
156
|
+
backoff = Engine::BackOff.new(ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS, 0, ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS) if with_backoff
|
|
157
|
+
|
|
158
|
+
loop do
|
|
159
|
+
remaining_attempts -= 1
|
|
160
|
+
|
|
161
|
+
result = @split_fetcher.fetch_splits(fetch_options)
|
|
162
|
+
|
|
163
|
+
return sync_result(true, remaining_attempts, result[:segment_names]) if target_cn <= @splits_repository.get_change_number
|
|
164
|
+
return sync_result(false, remaining_attempts, result[:segment_names]) if remaining_attempts <= 0
|
|
165
|
+
|
|
166
|
+
delay = with_backoff ? backoff.interval : retry_delay_seconds
|
|
167
|
+
sleep(delay)
|
|
168
|
+
end
|
|
70
169
|
end
|
|
71
170
|
|
|
72
171
|
# Starts thread which loops constantly and sends impressions to the Split API
|
|
@@ -87,6 +186,17 @@ module SplitIoClient
|
|
|
87
186
|
def start_telemetry_sync_task
|
|
88
187
|
Telemetry::SyncTask.new(@config, @telemetry_synchronizer).call
|
|
89
188
|
end
|
|
189
|
+
|
|
190
|
+
def sync_result(success, remaining_attempts, segment_names = nil)
|
|
191
|
+
{ success: success, remaining_attempts: remaining_attempts, segment_names: segment_names }
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def sync_splits_and_segments
|
|
195
|
+
@config.logger.debug('Synchronizing Splits and Segments ...') if @config.debug_enabled
|
|
196
|
+
splits_result = @split_fetcher.fetch_splits
|
|
197
|
+
|
|
198
|
+
splits_result[:success] && @segment_fetcher.fetch_segments
|
|
199
|
+
end
|
|
90
200
|
end
|
|
91
201
|
end
|
|
92
202
|
end
|
|
@@ -4,9 +4,9 @@ module SplitIoClient
|
|
|
4
4
|
# Creates a new split manager instance that connects to split.io API.
|
|
5
5
|
#
|
|
6
6
|
# @return [SplitIoManager] split.io client instance
|
|
7
|
-
def initialize(splits_repository = nil,
|
|
7
|
+
def initialize(splits_repository = nil, status_manager, config)
|
|
8
8
|
@splits_repository = splits_repository
|
|
9
|
-
@
|
|
9
|
+
@status_manager = status_manager
|
|
10
10
|
@config = config
|
|
11
11
|
end
|
|
12
12
|
|
|
@@ -78,7 +78,7 @@ module SplitIoClient
|
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
def block_until_ready(time = nil)
|
|
81
|
-
@
|
|
81
|
+
@status_manager.wait_until_ready(time) if @status_manager
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
private
|
|
@@ -111,7 +111,7 @@ module SplitIoClient
|
|
|
111
111
|
|
|
112
112
|
# move to blocker, alongside block until ready to avoid duplication
|
|
113
113
|
def ready?
|
|
114
|
-
return @
|
|
114
|
+
return @status_manager.ready? if @status_manager
|
|
115
115
|
true
|
|
116
116
|
end
|
|
117
117
|
end
|
|
@@ -113,6 +113,9 @@ module SplitIoClient
|
|
|
113
113
|
|
|
114
114
|
@sdk_start_time = Time.now
|
|
115
115
|
|
|
116
|
+
@on_demand_fetch_retry_delay_seconds = SplitConfig.default_on_demand_fetch_retry_delay_seconds
|
|
117
|
+
@on_demand_fetch_max_retries = SplitConfig.default_on_demand_fetch_max_retries
|
|
118
|
+
|
|
116
119
|
startup_log
|
|
117
120
|
end
|
|
118
121
|
|
|
@@ -278,6 +281,17 @@ module SplitIoClient
|
|
|
278
281
|
|
|
279
282
|
attr_accessor :sdk_start_time
|
|
280
283
|
|
|
284
|
+
attr_accessor :on_demand_fetch_retry_delay_seconds
|
|
285
|
+
attr_accessor :on_demand_fetch_max_retries
|
|
286
|
+
|
|
287
|
+
def self.default_on_demand_fetch_retry_delay_seconds
|
|
288
|
+
0.05
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
def self.default_on_demand_fetch_max_retries
|
|
292
|
+
10
|
|
293
|
+
end
|
|
294
|
+
|
|
281
295
|
def self.default_impressions_mode
|
|
282
296
|
:optimized
|
|
283
297
|
end
|
|
@@ -34,16 +34,16 @@ module SplitIoClient
|
|
|
34
34
|
@segments_repository = SegmentsRepository.new(@config)
|
|
35
35
|
@impressions_repository = ImpressionsRepository.new(@config)
|
|
36
36
|
@events_repository = EventsRepository.new(@config, @api_key, @runtime_producer)
|
|
37
|
-
@sdk_blocker = SDKBlocker.new(@splits_repository, @segments_repository, @config)
|
|
38
37
|
@impression_counter = SplitIoClient::Engine::Common::ImpressionCounter.new
|
|
39
38
|
@impressions_manager = SplitIoClient::Engine::Common::ImpressionManager.new(@config, @impressions_repository, @impression_counter, @runtime_producer)
|
|
40
39
|
@telemetry_api = SplitIoClient::Api::TelemetryApi.new(@config, @api_key, @runtime_producer)
|
|
41
40
|
@telemetry_synchronizer = Telemetry::Synchronizer.new(@config, @telemetry_consumers, @init_producer, repositories, @telemetry_api)
|
|
41
|
+
@status_manager = Engine::StatusManager.new(@config)
|
|
42
42
|
|
|
43
43
|
start!
|
|
44
44
|
|
|
45
|
-
@client = SplitClient.new(@api_key, repositories, @
|
|
46
|
-
@manager = SplitManager.new(@splits_repository, @
|
|
45
|
+
@client = SplitClient.new(@api_key, repositories, @status_manager, @config, @impressions_manager, @evaluation_producer)
|
|
46
|
+
@manager = SplitManager.new(@splits_repository, @status_manager, @config)
|
|
47
47
|
|
|
48
48
|
validate_api_key
|
|
49
49
|
|
|
@@ -51,22 +51,20 @@ module SplitIoClient
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def start!
|
|
54
|
-
if @config.localhost_mode
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
SplitIoClient::Engine::SyncManager.new(repositories, @api_key, @config, synchronizer, @runtime_producer, @sdk_blocker, @telemetry_synchronizer).start
|
|
69
|
-
end
|
|
54
|
+
return start_localhost_components if @config.localhost_mode
|
|
55
|
+
|
|
56
|
+
split_fetcher = SplitFetcher.new(@splits_repository, @api_key, config, @runtime_producer)
|
|
57
|
+
segment_fetcher = SegmentFetcher.new(@segments_repository, @api_key, config, @runtime_producer)
|
|
58
|
+
params = {
|
|
59
|
+
split_fetcher: split_fetcher,
|
|
60
|
+
segment_fetcher: segment_fetcher,
|
|
61
|
+
imp_counter: @impression_counter,
|
|
62
|
+
telemetry_runtime_producer: @runtime_producer,
|
|
63
|
+
telemetry_synchronizer: @telemetry_synchronizer
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
synchronizer = SplitIoClient::Engine::Synchronizer.new(repositories, @api_key, @config, params)
|
|
67
|
+
SplitIoClient::Engine::SyncManager.new(repositories, @api_key, @config, synchronizer, @runtime_producer, @telemetry_synchronizer, @status_manager).start
|
|
70
68
|
end
|
|
71
69
|
|
|
72
70
|
def stop!
|
|
@@ -145,7 +143,7 @@ module SplitIoClient
|
|
|
145
143
|
end
|
|
146
144
|
|
|
147
145
|
def start_localhost_components
|
|
148
|
-
LocalhostSplitStore.new(@splits_repository, @config, @
|
|
146
|
+
LocalhostSplitStore.new(@splits_repository, @config, @status_manager).call
|
|
149
147
|
|
|
150
148
|
# Starts thread which loops constantly and cleans up repositories to avoid memory issues in localhost mode
|
|
151
149
|
LocalhostRepoCleaner.new(@impressions_repository, @events_repository, @config).call
|
|
@@ -77,7 +77,7 @@ module SplitIoClient
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
def connect_stream(latch)
|
|
80
|
-
socket_write
|
|
80
|
+
socket_write(latch)
|
|
81
81
|
|
|
82
82
|
while connected? || @first_event.value
|
|
83
83
|
begin
|
|
@@ -96,13 +96,14 @@ module SplitIoClient
|
|
|
96
96
|
end
|
|
97
97
|
end
|
|
98
98
|
|
|
99
|
-
def socket_write
|
|
99
|
+
def socket_write(latch)
|
|
100
100
|
@first_event.make_true
|
|
101
101
|
@socket = socket_connect
|
|
102
102
|
@socket.write(build_request(@uri))
|
|
103
103
|
rescue StandardError => e
|
|
104
104
|
@config.logger.error("Error during connecting to #{@uri.host}. Error: #{e.inspect}")
|
|
105
105
|
close(Constants::PUSH_NONRETRYABLE_ERROR)
|
|
106
|
+
latch.count_down
|
|
106
107
|
end
|
|
107
108
|
|
|
108
109
|
def read_first_event(data, latch)
|
|
@@ -51,11 +51,7 @@ module SplitIoClient
|
|
|
51
51
|
cn = item[:change_number]
|
|
52
52
|
@config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}")
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
while cn > @segments_repository.get_change_number(segment_name).to_i && attempt <= Workers::MAX_RETRIES_ALLOWED
|
|
56
|
-
@synchronizer.fetch_segment(segment_name)
|
|
57
|
-
attempt += 1
|
|
58
|
-
end
|
|
54
|
+
@synchronizer.fetch_segment(segment_name, cn)
|
|
59
55
|
end
|
|
60
56
|
end
|
|
61
57
|
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
module SplitIoClient
|
|
4
4
|
module SSE
|
|
5
5
|
module Workers
|
|
6
|
-
MAX_RETRIES_ALLOWED = 10
|
|
7
|
-
|
|
8
6
|
class SplitsWorker
|
|
9
7
|
def initialize(synchronizer, config, splits_repository)
|
|
10
8
|
@synchronizer = synchronizer
|
|
@@ -62,12 +60,7 @@ module SplitIoClient
|
|
|
62
60
|
def perform
|
|
63
61
|
while (change_number = @queue.pop)
|
|
64
62
|
@config.logger.debug("SplitsWorker change_number dequeue #{change_number}")
|
|
65
|
-
|
|
66
|
-
attempt = 0
|
|
67
|
-
while change_number > @splits_repository.get_change_number.to_i && attempt <= Workers::MAX_RETRIES_ALLOWED
|
|
68
|
-
@synchronizer.fetch_splits
|
|
69
|
-
attempt += 1
|
|
70
|
-
end
|
|
63
|
+
@synchronizer.fetch_splits(change_number)
|
|
71
64
|
end
|
|
72
65
|
end
|
|
73
66
|
|
data/lib/splitclient-rb.rb
CHANGED
|
@@ -28,10 +28,9 @@ require 'splitclient-rb/cache/senders/impressions_sender'
|
|
|
28
28
|
require 'splitclient-rb/cache/senders/events_sender'
|
|
29
29
|
require 'splitclient-rb/cache/senders/impressions_count_sender'
|
|
30
30
|
require 'splitclient-rb/cache/senders/localhost_repo_cleaner'
|
|
31
|
-
require 'splitclient-rb/cache/stores/store_utils'
|
|
32
31
|
require 'splitclient-rb/cache/stores/localhost_split_builder'
|
|
33
|
-
require 'splitclient-rb/cache/stores/sdk_blocker'
|
|
34
32
|
require 'splitclient-rb/cache/stores/localhost_split_store'
|
|
33
|
+
require 'splitclient-rb/cache/stores/store_utils'
|
|
35
34
|
|
|
36
35
|
require 'splitclient-rb/clients/split_client'
|
|
37
36
|
require 'splitclient-rb/managers/split_manager'
|
|
@@ -85,13 +84,14 @@ require 'splitclient-rb/engine/models/split'
|
|
|
85
84
|
require 'splitclient-rb/engine/models/label'
|
|
86
85
|
require 'splitclient-rb/engine/models/treatment'
|
|
87
86
|
require 'splitclient-rb/engine/auth_api_client'
|
|
87
|
+
require 'splitclient-rb/engine/back_off'
|
|
88
88
|
require 'splitclient-rb/engine/push_manager'
|
|
89
|
+
require 'splitclient-rb/engine/status_manager'
|
|
89
90
|
require 'splitclient-rb/engine/sync_manager'
|
|
90
91
|
require 'splitclient-rb/engine/synchronizer'
|
|
91
92
|
require 'splitclient-rb/utilitites'
|
|
92
93
|
|
|
93
|
-
# SSE
|
|
94
|
-
require 'splitclient-rb/sse/event_source/back_off'
|
|
94
|
+
# SSE
|
|
95
95
|
require 'splitclient-rb/sse/event_source/client'
|
|
96
96
|
require 'splitclient-rb/sse/event_source/event_parser'
|
|
97
97
|
require 'splitclient-rb/sse/event_source/event_types'
|
data/splitclient-rb.gemspec
CHANGED
|
@@ -38,14 +38,15 @@ Gem::Specification.new do |spec|
|
|
|
38
38
|
|
|
39
39
|
spec.add_development_dependency 'allocation_stats'
|
|
40
40
|
spec.add_development_dependency 'appraisal'
|
|
41
|
-
spec.add_development_dependency 'bundler', '~>
|
|
41
|
+
spec.add_development_dependency 'bundler', '~> 2.2'
|
|
42
42
|
spec.add_development_dependency 'pry'
|
|
43
43
|
spec.add_development_dependency 'pry-nav'
|
|
44
44
|
spec.add_development_dependency 'rake', '12.3.3'
|
|
45
45
|
spec.add_development_dependency 'rake-compiler'
|
|
46
46
|
spec.add_development_dependency 'rspec'
|
|
47
47
|
spec.add_development_dependency 'rubocop', '0.59.0'
|
|
48
|
-
spec.add_development_dependency 'simplecov'
|
|
48
|
+
spec.add_development_dependency 'simplecov', '0.20.0'
|
|
49
|
+
spec.add_development_dependency 'simplecov-json'
|
|
49
50
|
spec.add_development_dependency 'timecop'
|
|
50
51
|
spec.add_development_dependency 'webmock'
|
|
51
52
|
|
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.3.
|
|
4
|
+
version: 7.3.2.pre.rc2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Split Software
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2021-
|
|
11
|
+
date: 2021-11-24 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: allocation_stats
|
|
@@ -44,14 +44,14 @@ dependencies:
|
|
|
44
44
|
requirements:
|
|
45
45
|
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
47
|
+
version: '2.2'
|
|
48
48
|
type: :development
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
54
|
+
version: '2.2'
|
|
55
55
|
- !ruby/object:Gem::Dependency
|
|
56
56
|
name: pry
|
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -138,6 +138,20 @@ dependencies:
|
|
|
138
138
|
version: 0.59.0
|
|
139
139
|
- !ruby/object:Gem::Dependency
|
|
140
140
|
name: simplecov
|
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
|
142
|
+
requirements:
|
|
143
|
+
- - '='
|
|
144
|
+
- !ruby/object:Gem::Version
|
|
145
|
+
version: 0.20.0
|
|
146
|
+
type: :development
|
|
147
|
+
prerelease: false
|
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
149
|
+
requirements:
|
|
150
|
+
- - '='
|
|
151
|
+
- !ruby/object:Gem::Version
|
|
152
|
+
version: 0.20.0
|
|
153
|
+
- !ruby/object:Gem::Dependency
|
|
154
|
+
name: simplecov-json
|
|
141
155
|
requirement: !ruby/object:Gem::Requirement
|
|
142
156
|
requirements:
|
|
143
157
|
- - ">="
|
|
@@ -313,10 +327,10 @@ extensions:
|
|
|
313
327
|
extra_rdoc_files: []
|
|
314
328
|
files:
|
|
315
329
|
- ".github/pull_request_template.md"
|
|
330
|
+
- ".github/workflows/ci.yml"
|
|
316
331
|
- ".gitignore"
|
|
317
332
|
- ".rubocop.yml"
|
|
318
333
|
- ".simplecov"
|
|
319
|
-
- ".travis.yml"
|
|
320
334
|
- Appraisals
|
|
321
335
|
- CHANGES.txt
|
|
322
336
|
- CONTRIBUTORS-GUIDE.md
|
|
@@ -361,7 +375,6 @@ files:
|
|
|
361
375
|
- lib/splitclient-rb/cache/senders/localhost_repo_cleaner.rb
|
|
362
376
|
- lib/splitclient-rb/cache/stores/localhost_split_builder.rb
|
|
363
377
|
- lib/splitclient-rb/cache/stores/localhost_split_store.rb
|
|
364
|
-
- lib/splitclient-rb/cache/stores/sdk_blocker.rb
|
|
365
378
|
- lib/splitclient-rb/cache/stores/store_utils.rb
|
|
366
379
|
- lib/splitclient-rb/clients/split_client.rb
|
|
367
380
|
- lib/splitclient-rb/constants.rb
|
|
@@ -374,6 +387,7 @@ files:
|
|
|
374
387
|
- lib/splitclient-rb/engine/api/splits.rb
|
|
375
388
|
- lib/splitclient-rb/engine/api/telemetry_api.rb
|
|
376
389
|
- lib/splitclient-rb/engine/auth_api_client.rb
|
|
390
|
+
- lib/splitclient-rb/engine/back_off.rb
|
|
377
391
|
- lib/splitclient-rb/engine/common/impressions_counter.rb
|
|
378
392
|
- lib/splitclient-rb/engine/common/impressions_manager.rb
|
|
379
393
|
- lib/splitclient-rb/engine/evaluator/splitter.rb
|
|
@@ -407,6 +421,7 @@ files:
|
|
|
407
421
|
- lib/splitclient-rb/engine/parser/evaluator.rb
|
|
408
422
|
- lib/splitclient-rb/engine/parser/partition.rb
|
|
409
423
|
- lib/splitclient-rb/engine/push_manager.rb
|
|
424
|
+
- lib/splitclient-rb/engine/status_manager.rb
|
|
410
425
|
- lib/splitclient-rb/engine/sync_manager.rb
|
|
411
426
|
- lib/splitclient-rb/engine/synchronizer.rb
|
|
412
427
|
- lib/splitclient-rb/exceptions.rb
|
|
@@ -417,7 +432,6 @@ files:
|
|
|
417
432
|
- lib/splitclient-rb/split_factory_builder.rb
|
|
418
433
|
- lib/splitclient-rb/split_factory_registry.rb
|
|
419
434
|
- lib/splitclient-rb/split_logger.rb
|
|
420
|
-
- lib/splitclient-rb/sse/event_source/back_off.rb
|
|
421
435
|
- lib/splitclient-rb/sse/event_source/client.rb
|
|
422
436
|
- lib/splitclient-rb/sse/event_source/event_parser.rb
|
|
423
437
|
- lib/splitclient-rb/sse/event_source/event_types.rb
|
|
@@ -451,7 +465,6 @@ files:
|
|
|
451
465
|
- lib/splitclient-rb/utilitites.rb
|
|
452
466
|
- lib/splitclient-rb/validators.rb
|
|
453
467
|
- lib/splitclient-rb/version.rb
|
|
454
|
-
- sonar-scanner.sh
|
|
455
468
|
- splitclient-rb.gemspec
|
|
456
469
|
- tasks/benchmark_get_treatment.rake
|
|
457
470
|
- tasks/irb.rake
|
|
@@ -459,7 +472,7 @@ homepage: https://github.com/splitio/ruby-client
|
|
|
459
472
|
licenses:
|
|
460
473
|
- Apache-2.0
|
|
461
474
|
metadata: {}
|
|
462
|
-
post_install_message:
|
|
475
|
+
post_install_message:
|
|
463
476
|
rdoc_options: []
|
|
464
477
|
require_paths:
|
|
465
478
|
- lib
|
|
@@ -470,12 +483,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
470
483
|
version: '0'
|
|
471
484
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
472
485
|
requirements:
|
|
473
|
-
- - "
|
|
486
|
+
- - ">"
|
|
474
487
|
- !ruby/object:Gem::Version
|
|
475
|
-
version:
|
|
488
|
+
version: 1.3.1
|
|
476
489
|
requirements: []
|
|
477
|
-
rubygems_version: 3.
|
|
478
|
-
signing_key:
|
|
490
|
+
rubygems_version: 3.2.32
|
|
491
|
+
signing_key:
|
|
479
492
|
specification_version: 4
|
|
480
493
|
summary: Ruby client for split SDK.
|
|
481
494
|
test_files: []
|
data/.travis.yml
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
language: ruby
|
|
2
|
-
|
|
3
|
-
rvm:
|
|
4
|
-
- "2.3.6"
|
|
5
|
-
|
|
6
|
-
services:
|
|
7
|
-
- redis-server
|
|
8
|
-
|
|
9
|
-
addons:
|
|
10
|
-
sonarqube: true
|
|
11
|
-
|
|
12
|
-
git:
|
|
13
|
-
depth: false
|
|
14
|
-
|
|
15
|
-
before_install:
|
|
16
|
-
- gem uninstall -v '>= 2' -i $(rvm gemdir)@global -ax bundler || true
|
|
17
|
-
- gem install bundler -v '< 2'
|
|
18
|
-
|
|
19
|
-
after_success:
|
|
20
|
-
- bash sonar-scanner.sh
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
require 'thread'
|
|
2
|
-
require 'timeout'
|
|
3
|
-
|
|
4
|
-
module SplitIoClient
|
|
5
|
-
module Cache
|
|
6
|
-
module Stores
|
|
7
|
-
class SDKBlocker
|
|
8
|
-
attr_reader :splits_repository
|
|
9
|
-
|
|
10
|
-
def initialize(splits_repository, segments_repository, config)
|
|
11
|
-
@splits_repository = splits_repository
|
|
12
|
-
@segments_repository = segments_repository
|
|
13
|
-
@config = config
|
|
14
|
-
@internal_ready = Concurrent::CountDownLatch.new(1)
|
|
15
|
-
|
|
16
|
-
if @config.standalone?
|
|
17
|
-
@splits_repository.not_ready!
|
|
18
|
-
@segments_repository.not_ready!
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def splits_ready!
|
|
23
|
-
if !ready?
|
|
24
|
-
@splits_repository.ready!
|
|
25
|
-
@config.logger.info('splits are ready')
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def segments_ready!
|
|
30
|
-
if !ready?
|
|
31
|
-
@segments_repository.ready!
|
|
32
|
-
@config.logger.info('segments are ready')
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def block(time = nil)
|
|
37
|
-
begin
|
|
38
|
-
timeout = time || @config.block_until_ready
|
|
39
|
-
Timeout::timeout(timeout) do
|
|
40
|
-
sleep 0.1 until ready?
|
|
41
|
-
end
|
|
42
|
-
rescue Timeout::Error
|
|
43
|
-
fail SDKBlockerTimeoutExpiredException, 'SDK start up timeout expired'
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
@config.logger.info('SplitIO SDK is ready')
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def ready?
|
|
50
|
-
return true if @config.consumer?
|
|
51
|
-
@splits_repository.ready? && @segments_repository.ready?
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def sdk_internal_ready
|
|
55
|
-
@internal_ready.count_down
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def wait_unitil_internal_ready
|
|
59
|
-
@internal_ready.wait
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: false
|
|
2
|
-
|
|
3
|
-
module SplitIoClient
|
|
4
|
-
module SSE
|
|
5
|
-
module EventSource
|
|
6
|
-
class BackOff
|
|
7
|
-
def initialize(back_off_base, attempt = 0)
|
|
8
|
-
@attempt = attempt
|
|
9
|
-
@back_off_base = back_off_base
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def interval
|
|
13
|
-
interval = (@back_off_base * (2**@attempt)) if @attempt.positive?
|
|
14
|
-
@attempt += 1
|
|
15
|
-
|
|
16
|
-
interval || 0
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def reset
|
|
20
|
-
@attempt = 0
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
|
25
|
-
end
|
data/sonar-scanner.sh
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
#/bin/bash -e
|
|
2
|
-
|
|
3
|
-
sonar_scanner() {
|
|
4
|
-
local params=$@
|
|
5
|
-
|
|
6
|
-
sonar-scanner \
|
|
7
|
-
-Dsonar.host.url='https://sonarqube.split-internal.com' \
|
|
8
|
-
-Dsonar.login="$SONAR_TOKEN" \
|
|
9
|
-
-Dsonar.ws.timeout='300' \
|
|
10
|
-
-Dsonar.sources='lib' \
|
|
11
|
-
-Dsonar.projectName='ruby-client' \
|
|
12
|
-
-Dsonar.projectKey='ruby-client' \
|
|
13
|
-
-Dsonar.ruby.coverage.reportPaths='coverage/.resultset.json' \
|
|
14
|
-
-Dsonar.links.ci='https://travis-ci.com/splitio/ruby-client' \
|
|
15
|
-
-Dsonar.links.scm='https://github.com/splitio/ruby-client' \
|
|
16
|
-
${params}
|
|
17
|
-
|
|
18
|
-
return $?
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then
|
|
22
|
-
sonar_scanner \
|
|
23
|
-
-Dsonar.pullrequest.provider='GitHub' \
|
|
24
|
-
-Dsonar.pullrequest.github.repository='splitio/ruby-client' \
|
|
25
|
-
-Dsonar.pullrequest.key=$TRAVIS_PULL_REQUEST \
|
|
26
|
-
-Dsonar.pullrequest.branch=$TRAVIS_PULL_REQUEST_BRANCH \
|
|
27
|
-
-Dsonar.pullrequest.base=$TRAVIS_BRANCH
|
|
28
|
-
else
|
|
29
|
-
if [ "$TRAVIS_BRANCH" == 'master' ]; then
|
|
30
|
-
sonar_scanner \
|
|
31
|
-
-Dsonar.branch.name=$TRAVIS_BRANCH
|
|
32
|
-
else
|
|
33
|
-
if [ "$TRAVIS_BRANCH" == 'development' ]; then
|
|
34
|
-
TARGET_BRANCH='master'
|
|
35
|
-
else
|
|
36
|
-
TARGET_BRANCH='development'
|
|
37
|
-
fi
|
|
38
|
-
sonar_scanner \
|
|
39
|
-
-Dsonar.branch.name=$TRAVIS_BRANCH \
|
|
40
|
-
-Dsonar.branch.target=$TARGET_BRANCH
|
|
41
|
-
fi
|
|
42
|
-
fi
|