splitclient-rb 7.3.0 → 7.3.1.pre.rc1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf1cbb00018b7c3ea1bd9a1ac48076b764b52efa752133d989710bc1d70a97a7
4
- data.tar.gz: 00c30f557c6beafea726021fc1a191c66cc9af8c1bdd4258afd06d636a6305c1
3
+ metadata.gz: 2de576884003cfed4ba2398d7f0909652f042eea9c2801fe4a835241f047a25d
4
+ data.tar.gz: 06eaa9744fea0d744eb71e36314e32c7750195585acf65ef736a30031483754e
5
5
  SHA512:
6
- metadata.gz: 385d7baa71c33a01e6bf877bcb400f63cc0481d49841e7861b7ced80b72f817918e776e656f7295f1e4c0c75a0875d65b08de20f018d81bbc50dd6fdf7e43771
7
- data.tar.gz: f3d13b7eab32c3352b02ecfaf91da3b23acf168990e20f8a58c2c3753b2a55acce4061ae97c4eb7511c3262e65ab311ac75066f4b1438f68f086f7c5fe41fee7
6
+ metadata.gz: 697eade28108c8aded43cf86e9114a64cf5614c577c776108014d8fcccccdb7e031d9c73305d91ce1ca0de3f78e3eeb5a7f477d28f5314f347457bb71b46bd5e
7
+ data.tar.gz: bb91fcc75c8c0f5e668e972f91264af9ab5e6684c2dea135ac9c24a9b39d9486ae0a899367d5a65bd996f812df285a1566f2914d58a6bf845516f697b9cecd24
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
@@ -85,13 +85,13 @@ require 'splitclient-rb/engine/models/split'
85
85
  require 'splitclient-rb/engine/models/label'
86
86
  require 'splitclient-rb/engine/models/treatment'
87
87
  require 'splitclient-rb/engine/auth_api_client'
88
+ require 'splitclient-rb/engine/back_off'
88
89
  require 'splitclient-rb/engine/push_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'
@@ -30,16 +30,19 @@ module SplitIoClient
30
30
  def fetch_segments_if_not_exists(names, cache_control_headers = false)
31
31
  names.each do |name|
32
32
  change_number = @segments_repository.get_change_number(name)
33
-
34
- fetch_segment(name, cache_control_headers) if change_number == -1
33
+
34
+ if change_number == -1
35
+ fetch_options = { cache_control_headers: cache_control_headers, till: nil }
36
+ fetch_segment(name, fetch_options) if change_number == -1
37
+ end
35
38
  end
36
39
  rescue StandardError => error
37
40
  @config.log_found_exception(__method__.to_s, error)
38
41
  end
39
42
 
40
- def fetch_segment(name, cache_control_headers = false)
43
+ def fetch_segment(name, fetch_options = { cache_control_headers: false, till: nil })
41
44
  @semaphore.synchronize do
42
- segments_api.fetch_segments_by_names([name], cache_control_headers)
45
+ segments_api.fetch_segments_by_names([name], fetch_options)
43
46
  end
44
47
  rescue StandardError => error
45
48
  @config.log_found_exception(__method__.to_s, error)
@@ -27,9 +27,9 @@ module SplitIoClient
27
27
  end
28
28
  end
29
29
 
30
- def fetch_splits(cache_control_headers = false)
30
+ def fetch_splits(fetch_options = { cache_control_headers: false, till: nil })
31
31
  @semaphore.synchronize do
32
- data = splits_since(@splits_repository.get_change_number, cache_control_headers)
32
+ data = splits_since(@splits_repository.get_change_number, fetch_options)
33
33
 
34
34
  data[:splits] && data[:splits].each do |split|
35
35
  add_split_unless_archived(split)
@@ -68,8 +68,8 @@ module SplitIoClient
68
68
  end
69
69
  end
70
70
 
71
- def splits_since(since, cache_control_headers = false)
72
- splits_api.since(since, cache_control_headers)
71
+ def splits_since(since, fetch_options = { cache_control_headers: false, till: nil })
72
+ splits_api.since(since, fetch_options)
73
73
  end
74
74
 
75
75
  def add_split_unless_archived(split)
@@ -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, cache_control_headers = false)
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, cache_control_headers)
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, cache_control_headers = false)
36
+ def fetch_segment_changes(name, since, fetch_options = { cache_control_headers: false, till: nil })
36
37
  start = Time.now
37
- response = get_api("#{@config.base_uri}/segmentChanges/#{name}", @api_key, { since: since }, cache_control_headers)
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, cache_control_headers = false)
13
+ def since(since, fetch_options = { cache_control_headers: false, till: nil })
14
14
  start = Time.now
15
-
16
- response = get_api("#{@config.base_uri}/splitChanges", @api_key, { since: since }, cache_control_headers)
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
@@ -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 = SplitIoClient::SSE::EventSource::BackOff.new(@config.auth_retry_back_off_base, 1)
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
 
@@ -6,7 +6,9 @@ module SplitIoClient
6
6
  include SplitIoClient::Cache::Fetchers
7
7
  include SplitIoClient::Cache::Senders
8
8
 
9
- FORCE_CACHE_CONTROL_HEADERS = true
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,
@@ -54,17 +56,116 @@ module SplitIoClient
54
56
  @segment_fetcher.stop_segments_thread
55
57
  end
56
58
 
57
- def fetch_splits
58
- segment_names = @split_fetcher.fetch_splits(FORCE_CACHE_CONTROL_HEADERS)
59
- @segment_fetcher.fetch_segments_if_not_exists(segment_names, FORCE_CACHE_CONTROL_HEADERS) unless segment_names.empty?
59
+ def fetch_splits(target_change_number)
60
+ return if target_change_number <= @splits_repository.get_change_number.to_i
61
+
62
+ fetch_options = { cache_control_headers: true, till: nil }
63
+
64
+ result = attempt_splits_sync(target_change_number,
65
+ fetch_options,
66
+ @config.on_demand_fetch_max_retries,
67
+ @config.on_demand_fetch_retry_delay_seconds,
68
+ false)
69
+
70
+ attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
71
+ if result[:success]
72
+ @segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty?
73
+ @config.logger.debug("Refresh completed in #{attempts} attempts.") if @config.debug_enabled
74
+
75
+ return
76
+ end
77
+
78
+ fetch_options[:till] = target_change_number
79
+ result = attempt_splits_sync(target_change_number,
80
+ fetch_options,
81
+ ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES,
82
+ nil,
83
+ true)
84
+
85
+ attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
86
+
87
+ if result[:success]
88
+ @segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty?
89
+ @config.logger.debug("Refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled
90
+ else
91
+ @config.logger.debug("No changes fetched after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled
92
+ end
93
+ rescue StandardError => error
94
+ @config.log_found_exception(__method__.to_s, error)
60
95
  end
61
96
 
62
- def fetch_segment(name)
63
- @segment_fetcher.fetch_segment(name, FORCE_CACHE_CONTROL_HEADERS)
97
+ def fetch_segment(name, target_change_number)
98
+ return if target_change_number <= @segments_repository.get_change_number(name).to_i
99
+
100
+ fetch_options = { cache_control_headers: true, till: nil }
101
+ result = attempt_segment_sync(name,
102
+ target_change_number,
103
+ fetch_options,
104
+ @config.on_demand_fetch_max_retries,
105
+ @config.on_demand_fetch_retry_delay_seconds,
106
+ false)
107
+
108
+ attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
109
+ if result[:success]
110
+ @config.logger.debug("Segment #{name} refresh completed in #{attempts} attempts.") if @config.debug_enabled
111
+
112
+ return
113
+ end
114
+
115
+ fetch_options = { cache_control_headers: true, till: target_change_number }
116
+ result = attempt_segment_sync(name,
117
+ target_change_number,
118
+ fetch_options,
119
+ ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES,
120
+ nil,
121
+ true)
122
+
123
+ attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts]
124
+ if result[:success]
125
+ @config.logger.debug("Segment #{name} refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled
126
+ else
127
+ @config.logger.debug("No changes fetched for segment #{name} after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled
128
+ end
129
+ rescue StandardError => error
130
+ @config.log_found_exception(__method__.to_s, error)
64
131
  end
65
132
 
66
133
  private
67
134
 
135
+ def attempt_segment_sync(name, target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff)
136
+ remaining_attempts = max_retries
137
+ backoff = Engine::BackOff.new(ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS, 0, ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS) if with_backoff
138
+
139
+ loop do
140
+ remaining_attempts -= 1
141
+
142
+ @segment_fetcher.fetch_segment(name, fetch_options)
143
+
144
+ return sync_result(true, remaining_attempts) if target_cn <= @segments_repository.get_change_number(name).to_i
145
+ return sync_result(false, remaining_attempts) if remaining_attempts <= 0
146
+
147
+ delay = with_backoff ? backoff.interval : retry_delay_seconds
148
+ sleep(delay)
149
+ end
150
+ end
151
+
152
+ def attempt_splits_sync(target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff)
153
+ remaining_attempts = max_retries
154
+ backoff = Engine::BackOff.new(ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS, 0, ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS) if with_backoff
155
+
156
+ loop do
157
+ remaining_attempts -= 1
158
+
159
+ segment_names = @split_fetcher.fetch_splits(fetch_options)
160
+
161
+ return sync_result(true, remaining_attempts, segment_names) if target_cn <= @splits_repository.get_change_number
162
+ return sync_result(false, remaining_attempts, segment_names) if remaining_attempts <= 0
163
+
164
+ delay = with_backoff ? backoff.interval : retry_delay_seconds
165
+ sleep(delay)
166
+ end
167
+ end
168
+
68
169
  def fetch_segments
69
170
  @segment_fetcher.fetch_segments
70
171
  end
@@ -87,6 +188,10 @@ module SplitIoClient
87
188
  def start_telemetry_sync_task
88
189
  Telemetry::SyncTask.new(@config, @telemetry_synchronizer).call
89
190
  end
191
+
192
+ def sync_result(success, remaining_attempts, segment_names = nil)
193
+ { success: success, remaining_attempts: remaining_attempts, segment_names: segment_names }
194
+ end
90
195
  end
91
196
  end
92
197
  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
@@ -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
- attempt = 0
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
 
@@ -1,3 +1,3 @@
1
1
  module SplitIoClient
2
- VERSION = '7.3.0'
2
+ VERSION = '7.3.1.pre.rc1'
3
3
  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.3.0
4
+ version: 7.3.1.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: 2021-07-13 00:00:00.000000000 Z
11
+ date: 2021-07-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: allocation_stats
@@ -374,6 +374,7 @@ files:
374
374
  - lib/splitclient-rb/engine/api/splits.rb
375
375
  - lib/splitclient-rb/engine/api/telemetry_api.rb
376
376
  - lib/splitclient-rb/engine/auth_api_client.rb
377
+ - lib/splitclient-rb/engine/back_off.rb
377
378
  - lib/splitclient-rb/engine/common/impressions_counter.rb
378
379
  - lib/splitclient-rb/engine/common/impressions_manager.rb
379
380
  - lib/splitclient-rb/engine/evaluator/splitter.rb
@@ -417,7 +418,6 @@ files:
417
418
  - lib/splitclient-rb/split_factory_builder.rb
418
419
  - lib/splitclient-rb/split_factory_registry.rb
419
420
  - lib/splitclient-rb/split_logger.rb
420
- - lib/splitclient-rb/sse/event_source/back_off.rb
421
421
  - lib/splitclient-rb/sse/event_source/client.rb
422
422
  - lib/splitclient-rb/sse/event_source/event_parser.rb
423
423
  - lib/splitclient-rb/sse/event_source/event_types.rb
@@ -470,9 +470,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
470
470
  version: '0'
471
471
  required_rubygems_version: !ruby/object:Gem::Requirement
472
472
  requirements:
473
- - - ">="
473
+ - - ">"
474
474
  - !ruby/object:Gem::Version
475
- version: '0'
475
+ version: 1.3.1
476
476
  requirements: []
477
477
  rubygems_version: 3.0.6
478
478
  signing_key:
@@ -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