Skip to content

Commit 7f7028b

Browse files
authored
Merge pull request #380 from splitio/splits-logic-for-retries
Splits: add new logic for retries and logic with cdn bypassed
2 parents 97e6448 + 7df074a commit 7f7028b

11 files changed

Lines changed: 144 additions & 24 deletions

File tree

.rubocop.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Metrics/LineLength:
3131
- spec/engine/sync_manager_spec.rb
3232
- spec/engine/auth_api_client_spec.rb
3333
- spec/telemetry/synchronizer_spec.rb
34+
- spec/splitclient/split_config_spec.rb
3435

3536
Style/BracesAroundHashParameters:
3637
Exclude:
@@ -62,3 +63,4 @@ AllCops:
6263
- lib/splitclient-rb/engine/models/**/*
6364
- lib/splitclient-rb/engine/parser/**/*
6465
- spec/telemetry/synchronizer_spec.rb
66+
- lib/splitclient-rb/engine/synchronizer.rb

lib/splitclient-rb/engine/synchronizer.rb

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ class Synchronizer
66
include SplitIoClient::Cache::Fetchers
77
include SplitIoClient::Cache::Senders
88

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
12+
913
def initialize(
1014
repositories,
1115
api_key,
@@ -52,10 +56,42 @@ def stop_periodic_fetch
5256
@segment_fetcher.stop_segments_thread
5357
end
5458

55-
def fetch_splits
59+
def fetch_splits(target_change_number)
60+
return if target_change_number <= @splits_repository.get_change_number.to_i
61+
5662
fetch_options = { cache_control_headers: true, till: nil }
57-
segment_names = @split_fetcher.fetch_splits(fetch_options)
58-
@segment_fetcher.fetch_segments_if_not_exists(segment_names, true) unless segment_names.empty?
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.")
90+
else
91+
@config.logger.debug("No changes fetched after #{attempts} attempts with CDN bypassed.")
92+
end
93+
rescue StandardError => error
94+
@config.log_found_exception(__method__.to_s, error)
5995
end
6096

6197
def fetch_segment(name)
@@ -65,6 +101,23 @@ def fetch_segment(name)
65101

66102
private
67103

104+
def attempt_splits_sync(target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff)
105+
remaining_attempts = max_retries
106+
backoff = SSE::EventSource::BackOff.new(ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS, 0, ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS) if with_backoff
107+
108+
loop do
109+
remaining_attempts -= 1
110+
111+
segment_names = @split_fetcher.fetch_splits(fetch_options)
112+
113+
return split_sync_result(true, remaining_attempts, segment_names) if target_cn <= @splits_repository.get_change_number
114+
return split_sync_result(false, remaining_attempts, segment_names) if remaining_attempts <= 0
115+
116+
delay = with_backoff ? backoff.interval : retry_delay_seconds
117+
sleep(delay)
118+
end
119+
end
120+
68121
def fetch_segments
69122
@segment_fetcher.fetch_segments
70123
end
@@ -87,6 +140,10 @@ def impressions_count_sender
87140
def start_telemetry_sync_task
88141
Telemetry::SyncTask.new(@config, @telemetry_synchronizer).call
89142
end
143+
144+
def split_sync_result(success, remaining_attempts, segment_names)
145+
{ success: success, remaining_attempts: remaining_attempts, segment_names: segment_names }
146+
end
90147
end
91148
end
92149
end

lib/splitclient-rb/split_config.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ def initialize(opts = {})
113113

114114
@sdk_start_time = Time.now
115115

116-
@on_demand_fetch_retry_delay_ms = SplitConfig.default_on_demand_fetch_retry_delay_ms
116+
@on_demand_fetch_retry_delay_seconds = SplitConfig.default_on_demand_fetch_retry_delay_seconds
117117
@on_demand_fetch_max_retries = SplitConfig.default_on_demand_fetch_max_retries
118118

119119
startup_log
@@ -281,11 +281,11 @@ def initialize(opts = {})
281281

282282
attr_accessor :sdk_start_time
283283

284-
attr_accessor :on_demand_fetch_retry_delay_ms
284+
attr_accessor :on_demand_fetch_retry_delay_seconds
285285
attr_accessor :on_demand_fetch_max_retries
286286

287-
def self.default_on_demand_fetch_retry_delay_ms
288-
50
287+
def self.default_on_demand_fetch_retry_delay_seconds
288+
0.05
289289
end
290290

291291
def self.default_on_demand_fetch_max_retries

lib/splitclient-rb/sse/event_source/back_off.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,20 @@
33
module SplitIoClient
44
module SSE
55
module EventSource
6+
BACKOFF_MAX_ALLOWED = 1.8
67
class BackOff
7-
def initialize(back_off_base, attempt = 0)
8+
def initialize(back_off_base, attempt = 0, max_allowed = BACKOFF_MAX_ALLOWED)
89
@attempt = attempt
910
@back_off_base = back_off_base
11+
@max_allowed = max_allowed
1012
end
1113

1214
def interval
15+
interval = 0
1316
interval = (@back_off_base * (2**@attempt)) if @attempt.positive?
1417
@attempt += 1
1518

16-
interval || 0
19+
interval >= @max_allowed ? @max_allowed : interval
1720
end
1821

1922
def reset

lib/splitclient-rb/sse/workers/segments_worker.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module SplitIoClient
44
module SSE
55
module Workers
6+
MAX_RETRIES_ALLOWED = 10
67
class SegmentsWorker
78
def initialize(synchronizer, config, segments_repository)
89
@synchronizer = synchronizer
@@ -52,7 +53,7 @@ def perform
5253
@config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}")
5354

5455
attempt = 0
55-
while cn > @segments_repository.get_change_number(segment_name).to_i && attempt <= Workers::MAX_RETRIES_ALLOWED
56+
while cn > @segments_repository.get_change_number(segment_name).to_i && attempt <= MAX_RETRIES_ALLOWED
5657
@synchronizer.fetch_segment(segment_name)
5758
attempt += 1
5859
end

lib/splitclient-rb/sse/workers/splits_worker.rb

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
module SplitIoClient
44
module SSE
55
module Workers
6-
MAX_RETRIES_ALLOWED = 10
7-
86
class SplitsWorker
97
def initialize(synchronizer, config, splits_repository)
108
@synchronizer = synchronizer
@@ -62,12 +60,7 @@ def kill_split(change_number, split_name, default_treatment)
6260
def perform
6361
while (change_number = @queue.pop)
6462
@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)
7164
end
7265
end
7366

spec/engine/synchronizer_spec.rb

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,43 @@
9292
mock_segment_changes('segment1', segment1, '-1')
9393
mock_segment_changes('segment1', segment1, '1470947453877')
9494

95-
synchronizer.fetch_splits
95+
synchronizer.fetch_splits(0)
9696
expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1')).to have_been_made.once
9797
end
9898

99+
it 'fetch_splits - ' do
100+
sync = subject.new(repositories, api_key, config, sdk_blocker, parameters)
101+
stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1')
102+
.to_return(status: 200, body:
103+
'{
104+
"splits": [],
105+
"since": -1,
106+
"till": 1506703262918
107+
}')
108+
109+
stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918')
110+
.to_return(status: 200, body:
111+
'{
112+
"splits": [],
113+
"since": 1506703262918,
114+
"till": 1506703262918
115+
}')
116+
117+
stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918&till=1506703262920')
118+
.to_return(status: 200, body:
119+
'{
120+
"splits": [],
121+
"since": 1506703262918,
122+
"till": 1506703262921
123+
}')
124+
125+
sync.fetch_splits(1_506_703_262_920)
126+
127+
expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1')).to have_been_made.once
128+
expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918')).to have_been_made.times(9)
129+
expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918&till=1506703262920')).to have_been_made.once
130+
end
131+
99132
it 'fetch_segment' do
100133
mock_segment_changes('segment3', segment3, '-1')
101134

spec/splitclient/split_config_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
expect(configs.ip_addresses_enabled).to eq default_ip
3232
expect(configs.machine_name).to eq SplitIoClient::SplitConfig.machine_hostname(default_ip, nil, :redis)
3333
expect(configs.machine_ip).to eq SplitIoClient::SplitConfig.machine_ip(default_ip, nil, :redis)
34-
expect(configs.on_demand_fetch_retry_delay_ms).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_retry_delay_ms
34+
expect(configs.on_demand_fetch_retry_delay_seconds).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_retry_delay_seconds
3535
expect(configs.on_demand_fetch_max_retries).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_max_retries
3636
end
3737

spec/sse/event_source/back_off_spec.rb

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
let(:log) { StringIO.new }
1010

1111
it 'get intervals and reset attemps' do
12-
back_off = subject.new(1)
12+
back_off = subject.new(1, 0, 5)
1313

1414
firts_interval = back_off.interval
1515
expect(firts_interval).to eq(0)
@@ -27,7 +27,7 @@
2727

2828
it 'with custom config' do
2929
streaming_reconnect_back_off_base = 5
30-
back_off = subject.new(streaming_reconnect_back_off_base)
30+
back_off = subject.new(streaming_reconnect_back_off_base, 0, 30)
3131

3232
firts_interval = back_off.interval
3333
expect(firts_interval).to eq(0)
@@ -42,4 +42,34 @@
4242
reset_interval = back_off.interval
4343
expect(reset_interval).to eq(0)
4444
end
45+
46+
it 'with max' do
47+
streaming_reconnect_back_off_base = 5
48+
back_off = subject.new(streaming_reconnect_back_off_base, 0, 30)
49+
50+
firts_interval = back_off.interval
51+
expect(firts_interval).to eq(0)
52+
53+
second_interval = back_off.interval
54+
expect(second_interval).to eq(10)
55+
56+
third_interval = back_off.interval
57+
expect(third_interval).to eq(20)
58+
59+
third_interval = back_off.interval
60+
expect(third_interval).to eq(30)
61+
62+
third_interval = back_off.interval
63+
expect(third_interval).to eq(30)
64+
65+
third_interval = back_off.interval
66+
expect(third_interval).to eq(30)
67+
68+
third_interval = back_off.interval
69+
expect(third_interval).to eq(30)
70+
71+
back_off.reset
72+
reset_interval = back_off.interval
73+
expect(reset_interval).to eq(0)
74+
end
4575
end

spec/sse/sse_handler_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
mock_segment_changes('segment2', segment2, '1470947453878')
6363
mock_segment_changes('segment3', segment3, '-1')
6464

65-
synchronizer.fetch_splits
65+
synchronizer.fetch_splits(0)
6666
end
6767

6868
context 'SPLIT UPDATE event' do

0 commit comments

Comments
 (0)