diff --git a/.rubocop.yml b/.rubocop.yml index 9806211df..74645582a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -31,6 +31,7 @@ Metrics/LineLength: - spec/engine/sync_manager_spec.rb - spec/engine/auth_api_client_spec.rb - spec/telemetry/synchronizer_spec.rb + - spec/splitclient/split_config_spec.rb Style/BracesAroundHashParameters: Exclude: @@ -62,3 +63,4 @@ AllCops: - lib/splitclient-rb/engine/models/**/* - lib/splitclient-rb/engine/parser/**/* - spec/telemetry/synchronizer_spec.rb + - lib/splitclient-rb/engine/synchronizer.rb diff --git a/CHANGES.txt b/CHANGES.txt index 2022cdd6b..0e9633e04 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,5 +1,8 @@ CHANGES +7.3.1 (Jul 26, 2021) +- 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. + 7.3.0 (Jul 12, 2021) - Updated SDK telemetry storage, metrics and updater to be more effective and send less often. - Fixed high cpu usage when api key is wrong. diff --git a/lib/splitclient-rb.rb b/lib/splitclient-rb.rb index d9657280e..a184a1732 100644 --- a/lib/splitclient-rb.rb +++ b/lib/splitclient-rb.rb @@ -85,13 +85,13 @@ require 'splitclient-rb/engine/models/label' require 'splitclient-rb/engine/models/treatment' require 'splitclient-rb/engine/auth_api_client' +require 'splitclient-rb/engine/back_off' require 'splitclient-rb/engine/push_manager' require 'splitclient-rb/engine/sync_manager' require 'splitclient-rb/engine/synchronizer' require 'splitclient-rb/utilitites' -# SSE -require 'splitclient-rb/sse/event_source/back_off' +# SSE require 'splitclient-rb/sse/event_source/client' require 'splitclient-rb/sse/event_source/event_parser' require 'splitclient-rb/sse/event_source/event_types' diff --git a/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb b/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb index 85ed13ceb..efd24bd2b 100644 --- a/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb +++ b/lib/splitclient-rb/cache/fetchers/segment_fetcher.rb @@ -30,16 +30,19 @@ def call def fetch_segments_if_not_exists(names, cache_control_headers = false) names.each do |name| change_number = @segments_repository.get_change_number(name) - - fetch_segment(name, cache_control_headers) if change_number == -1 + + if change_number == -1 + fetch_options = { cache_control_headers: cache_control_headers, till: nil } + fetch_segment(name, fetch_options) if change_number == -1 + end end rescue StandardError => error @config.log_found_exception(__method__.to_s, error) end - def fetch_segment(name, cache_control_headers = false) + def fetch_segment(name, fetch_options = { cache_control_headers: false, till: nil }) @semaphore.synchronize do - segments_api.fetch_segments_by_names([name], cache_control_headers) + segments_api.fetch_segments_by_names([name], fetch_options) end rescue StandardError => error @config.log_found_exception(__method__.to_s, error) diff --git a/lib/splitclient-rb/cache/fetchers/split_fetcher.rb b/lib/splitclient-rb/cache/fetchers/split_fetcher.rb index 91cb311bb..51534bab8 100644 --- a/lib/splitclient-rb/cache/fetchers/split_fetcher.rb +++ b/lib/splitclient-rb/cache/fetchers/split_fetcher.rb @@ -27,9 +27,9 @@ def call end end - def fetch_splits(cache_control_headers = false) + def fetch_splits(fetch_options = { cache_control_headers: false, till: nil }) @semaphore.synchronize do - data = splits_since(@splits_repository.get_change_number, cache_control_headers) + data = splits_since(@splits_repository.get_change_number, fetch_options) data[:splits] && data[:splits].each do |split| add_split_unless_archived(split) @@ -68,8 +68,8 @@ def splits_thread end end - def splits_since(since, cache_control_headers = false) - splits_api.since(since, cache_control_headers) + def splits_since(since, fetch_options = { cache_control_headers: false, till: nil }) + splits_api.since(since, fetch_options) end def add_split_unless_archived(split) diff --git a/lib/splitclient-rb/engine/api/segments.rb b/lib/splitclient-rb/engine/api/segments.rb index ae27453d3..7f4a2f8d2 100644 --- a/lib/splitclient-rb/engine/api/segments.rb +++ b/lib/splitclient-rb/engine/api/segments.rb @@ -11,13 +11,14 @@ def initialize(api_key, segments_repository, config, telemetry_runtime_producer) @telemetry_runtime_producer = telemetry_runtime_producer end - def fetch_segments_by_names(names, cache_control_headers = false) + def fetch_segments_by_names(names, fetch_options = { cache_control_headers: false, till: nil }) return if names.nil? || names.empty? names.each do |name| since = @segments_repository.get_change_number(name) + loop do - segment = fetch_segment_changes(name, since, cache_control_headers) + segment = fetch_segment_changes(name, since, fetch_options) @segments_repository.add_to_segment(segment) @config.split_logger.log_if_debug("Segment #{name} fetched before: #{since}, \ @@ -32,9 +33,12 @@ def fetch_segments_by_names(names, cache_control_headers = false) private - def fetch_segment_changes(name, since, cache_control_headers = false) + def fetch_segment_changes(name, since, fetch_options = { cache_control_headers: false, till: nil }) start = Time.now - response = get_api("#{@config.base_uri}/segmentChanges/#{name}", @api_key, { since: since }, cache_control_headers) + + params = { since: since } + params[:till] = fetch_options[:till] unless fetch_options[:till].nil? + response = get_api("#{@config.base_uri}/segmentChanges/#{name}", @api_key, params, fetch_options[:cache_control_headers]) if response.success? segment = JSON.parse(response.body, symbolize_names: true) diff --git a/lib/splitclient-rb/engine/api/splits.rb b/lib/splitclient-rb/engine/api/splits.rb index 3d0c1197f..99bbe5cee 100644 --- a/lib/splitclient-rb/engine/api/splits.rb +++ b/lib/splitclient-rb/engine/api/splits.rb @@ -10,10 +10,12 @@ def initialize(api_key, config, telemetry_runtime_producer) @telemetry_runtime_producer = telemetry_runtime_producer end - def since(since, cache_control_headers = false) + def since(since, fetch_options = { cache_control_headers: false, till: nil }) start = Time.now - - response = get_api("#{@config.base_uri}/splitChanges", @api_key, { since: since }, cache_control_headers) + + params = { since: since } + params[:till] = fetch_options[:till] unless fetch_options[:till].nil? + response = get_api("#{@config.base_uri}/splitChanges", @api_key, params, fetch_options[:cache_control_headers]) if response.success? result = splits_with_segment_names(response.body) diff --git a/lib/splitclient-rb/engine/back_off.rb b/lib/splitclient-rb/engine/back_off.rb new file mode 100644 index 000000000..31097a097 --- /dev/null +++ b/lib/splitclient-rb/engine/back_off.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: false + +module SplitIoClient + module Engine + BACKOFF_MAX_ALLOWED = 1.8 + class BackOff + def initialize(back_off_base, attempt = 0, max_allowed = BACKOFF_MAX_ALLOWED) + @attempt = attempt + @back_off_base = back_off_base + @max_allowed = max_allowed + end + + def interval + interval = 0 + interval = (@back_off_base * (2**@attempt)) if @attempt.positive? + @attempt += 1 + + interval >= @max_allowed ? @max_allowed : interval + end + + def reset + @attempt = 0 + end + end + end +end diff --git a/lib/splitclient-rb/engine/push_manager.rb b/lib/splitclient-rb/engine/push_manager.rb index 61f9eed3c..f30aa46e0 100644 --- a/lib/splitclient-rb/engine/push_manager.rb +++ b/lib/splitclient-rb/engine/push_manager.rb @@ -8,7 +8,7 @@ def initialize(config, sse_handler, api_key, telemetry_runtime_producer) @sse_handler = sse_handler @auth_api_client = AuthApiClient.new(@config, telemetry_runtime_producer) @api_key = api_key - @back_off = SplitIoClient::SSE::EventSource::BackOff.new(@config.auth_retry_back_off_base, 1) + @back_off = Engine::BackOff.new(@config.auth_retry_back_off_base, 1) @telemetry_runtime_producer = telemetry_runtime_producer end diff --git a/lib/splitclient-rb/engine/synchronizer.rb b/lib/splitclient-rb/engine/synchronizer.rb index 069521d7e..bbaae13ae 100644 --- a/lib/splitclient-rb/engine/synchronizer.rb +++ b/lib/splitclient-rb/engine/synchronizer.rb @@ -6,7 +6,9 @@ class Synchronizer include SplitIoClient::Cache::Fetchers include SplitIoClient::Cache::Senders - FORCE_CACHE_CONTROL_HEADERS = true + ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS = 10 + ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS = 60 + ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES = 10 def initialize( repositories, @@ -54,17 +56,116 @@ def stop_periodic_fetch @segment_fetcher.stop_segments_thread end - def fetch_splits - segment_names = @split_fetcher.fetch_splits(FORCE_CACHE_CONTROL_HEADERS) - @segment_fetcher.fetch_segments_if_not_exists(segment_names, FORCE_CACHE_CONTROL_HEADERS) unless segment_names.empty? + def fetch_splits(target_change_number) + return if target_change_number <= @splits_repository.get_change_number.to_i + + fetch_options = { cache_control_headers: true, till: nil } + + result = attempt_splits_sync(target_change_number, + fetch_options, + @config.on_demand_fetch_max_retries, + @config.on_demand_fetch_retry_delay_seconds, + false) + + attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts] + if result[:success] + @segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty? + @config.logger.debug("Refresh completed in #{attempts} attempts.") if @config.debug_enabled + + return + end + + fetch_options[:till] = target_change_number + result = attempt_splits_sync(target_change_number, + fetch_options, + ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES, + nil, + true) + + attempts = ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES - result[:remaining_attempts] + + if result[:success] + @segment_fetcher.fetch_segments_if_not_exists(result[:segment_names], true) unless result[:segment_names].empty? + @config.logger.debug("Refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled + else + @config.logger.debug("No changes fetched after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled + end + rescue StandardError => error + @config.log_found_exception(__method__.to_s, error) end - def fetch_segment(name) - @segment_fetcher.fetch_segment(name, FORCE_CACHE_CONTROL_HEADERS) + def fetch_segment(name, target_change_number) + return if target_change_number <= @segments_repository.get_change_number(name).to_i + + fetch_options = { cache_control_headers: true, till: nil } + result = attempt_segment_sync(name, + target_change_number, + fetch_options, + @config.on_demand_fetch_max_retries, + @config.on_demand_fetch_retry_delay_seconds, + false) + + attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts] + if result[:success] + @config.logger.debug("Segment #{name} refresh completed in #{attempts} attempts.") if @config.debug_enabled + + return + end + + fetch_options = { cache_control_headers: true, till: target_change_number } + result = attempt_segment_sync(name, + target_change_number, + fetch_options, + ON_DEMAND_FETCH_BACKOFF_MAX_RETRIES, + nil, + true) + + attempts = @config.on_demand_fetch_max_retries - result[:remaining_attempts] + if result[:success] + @config.logger.debug("Segment #{name} refresh completed bypassing the CDN in #{attempts} attempts.") if @config.debug_enabled + else + @config.logger.debug("No changes fetched for segment #{name} after #{attempts} attempts with CDN bypassed.") if @config.debug_enabled + end + rescue StandardError => error + @config.log_found_exception(__method__.to_s, error) end private + def attempt_segment_sync(name, target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff) + remaining_attempts = max_retries + backoff = Engine::BackOff.new(ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS, 0, ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS) if with_backoff + + loop do + remaining_attempts -= 1 + + @segment_fetcher.fetch_segment(name, fetch_options) + + return sync_result(true, remaining_attempts) if target_cn <= @segments_repository.get_change_number(name).to_i + return sync_result(false, remaining_attempts) if remaining_attempts <= 0 + + delay = with_backoff ? backoff.interval : retry_delay_seconds + sleep(delay) + end + end + + def attempt_splits_sync(target_cn, fetch_options, max_retries, retry_delay_seconds, with_backoff) + remaining_attempts = max_retries + backoff = Engine::BackOff.new(ON_DEMAND_FETCH_BACKOFF_BASE_SECONDS, 0, ON_DEMAND_FETCH_BACKOFF_MAX_WAIT_SECONDS) if with_backoff + + loop do + remaining_attempts -= 1 + + segment_names = @split_fetcher.fetch_splits(fetch_options) + + return sync_result(true, remaining_attempts, segment_names) if target_cn <= @splits_repository.get_change_number + return sync_result(false, remaining_attempts, segment_names) if remaining_attempts <= 0 + + delay = with_backoff ? backoff.interval : retry_delay_seconds + sleep(delay) + end + end + def fetch_segments @segment_fetcher.fetch_segments end @@ -87,6 +188,10 @@ def impressions_count_sender def start_telemetry_sync_task Telemetry::SyncTask.new(@config, @telemetry_synchronizer).call end + + def sync_result(success, remaining_attempts, segment_names = nil) + { success: success, remaining_attempts: remaining_attempts, segment_names: segment_names } + end end end end diff --git a/lib/splitclient-rb/split_config.rb b/lib/splitclient-rb/split_config.rb index 468c3b0af..837e8a680 100644 --- a/lib/splitclient-rb/split_config.rb +++ b/lib/splitclient-rb/split_config.rb @@ -113,6 +113,9 @@ def initialize(opts = {}) @sdk_start_time = Time.now + @on_demand_fetch_retry_delay_seconds = SplitConfig.default_on_demand_fetch_retry_delay_seconds + @on_demand_fetch_max_retries = SplitConfig.default_on_demand_fetch_max_retries + startup_log end @@ -278,6 +281,17 @@ def initialize(opts = {}) attr_accessor :sdk_start_time + attr_accessor :on_demand_fetch_retry_delay_seconds + attr_accessor :on_demand_fetch_max_retries + + def self.default_on_demand_fetch_retry_delay_seconds + 0.05 + end + + def self.default_on_demand_fetch_max_retries + 10 + end + def self.default_impressions_mode :optimized end diff --git a/lib/splitclient-rb/sse/event_source/back_off.rb b/lib/splitclient-rb/sse/event_source/back_off.rb deleted file mode 100644 index b9a2f8c02..000000000 --- a/lib/splitclient-rb/sse/event_source/back_off.rb +++ /dev/null @@ -1,25 +0,0 @@ -# frozen_string_literal: false - -module SplitIoClient - module SSE - module EventSource - class BackOff - def initialize(back_off_base, attempt = 0) - @attempt = attempt - @back_off_base = back_off_base - end - - def interval - interval = (@back_off_base * (2**@attempt)) if @attempt.positive? - @attempt += 1 - - interval || 0 - end - - def reset - @attempt = 0 - end - end - end - end -end diff --git a/lib/splitclient-rb/sse/workers/segments_worker.rb b/lib/splitclient-rb/sse/workers/segments_worker.rb index 6d5768e85..0a288ecc8 100644 --- a/lib/splitclient-rb/sse/workers/segments_worker.rb +++ b/lib/splitclient-rb/sse/workers/segments_worker.rb @@ -51,11 +51,7 @@ def perform cn = item[:change_number] @config.logger.debug("SegmentsWorker change_number dequeue #{segment_name}, #{cn}") - attempt = 0 - while cn > @segments_repository.get_change_number(segment_name).to_i && attempt <= Workers::MAX_RETRIES_ALLOWED - @synchronizer.fetch_segment(segment_name) - attempt += 1 - end + @synchronizer.fetch_segment(segment_name, cn) end end diff --git a/lib/splitclient-rb/sse/workers/splits_worker.rb b/lib/splitclient-rb/sse/workers/splits_worker.rb index ba4fd5273..03c780b14 100644 --- a/lib/splitclient-rb/sse/workers/splits_worker.rb +++ b/lib/splitclient-rb/sse/workers/splits_worker.rb @@ -3,8 +3,6 @@ module SplitIoClient module SSE module Workers - MAX_RETRIES_ALLOWED = 10 - class SplitsWorker def initialize(synchronizer, config, splits_repository) @synchronizer = synchronizer @@ -62,12 +60,7 @@ def kill_split(change_number, split_name, default_treatment) def perform while (change_number = @queue.pop) @config.logger.debug("SplitsWorker change_number dequeue #{change_number}") - - attempt = 0 - while change_number > @splits_repository.get_change_number.to_i && attempt <= Workers::MAX_RETRIES_ALLOWED - @synchronizer.fetch_splits - attempt += 1 - end + @synchronizer.fetch_splits(change_number) end end diff --git a/lib/splitclient-rb/version.rb b/lib/splitclient-rb/version.rb index b204d940f..0e1cd8aef 100644 --- a/lib/splitclient-rb/version.rb +++ b/lib/splitclient-rb/version.rb @@ -1,3 +1,3 @@ module SplitIoClient - VERSION = '7.3.0' + VERSION = '7.3.1' end diff --git a/spec/engine/api/segments_spec.rb b/spec/engine/api/segments_spec.rb index af112ae1a..755a2890f 100644 --- a/spec/engine/api/segments_spec.rb +++ b/spec/engine/api/segments_spec.rb @@ -43,6 +43,28 @@ expect(log.string).to include ':added=>["max", "dan"]' end + it 'returns fetch_segments - with till param' do + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/employees?since=-1&till=222334') + .with(headers: { + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip', + 'Authorization' => 'Bearer', + 'Connection' => 'keep-alive', + 'Keep-Alive' => '30', + 'Splitsdkversion' => "#{config.language}-#{config.version}" + }) + .to_return(status: 200, body: segments) + + fetch_options = { cache_control_headers: false, till: 222_334 } + returned_segment = segments_api.send(:fetch_segment_changes, 'employees', -1, fetch_options) + + expect(returned_segment[:name]).to eq 'employees' + + expect(log.string).to include "'employees' segment retrieved." + expect(log.string).to include "'employees' 2 added keys" + expect(log.string).to include ':added=>["max", "dan"]' + end + it 'returns fetch_segments - checking headers when cache_control_headers is true' do stub_request(:get, 'https://sdk.split.io/api/segmentChanges/employees?since=-1') .with(headers: { @@ -56,7 +78,8 @@ }) .to_return(status: 200, body: segments) - returned_segment = segments_api.send(:fetch_segment_changes, 'employees', -1, true) + fetch_options = { cache_control_headers: true, till: nil } + returned_segment = segments_api.send(:fetch_segment_changes, 'employees', -1, fetch_options) expect(returned_segment[:name]).to eq 'employees' diff --git a/spec/engine/api/splits_spec.rb b/spec/engine/api/splits_spec.rb index 374fda99c..bc2fcb034 100644 --- a/spec/engine/api/splits_spec.rb +++ b/spec/engine/api/splits_spec.rb @@ -47,7 +47,27 @@ expect(log.string).to include returned_splits.to_s end - it 'returns the splits - checking headers when cache_control_headers is true ' do + it 'returns the splits - with till param' do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1&till=123123') + .with(headers: { + 'Accept' => '*/*', + 'Accept-Encoding' => 'gzip', + 'Authorization' => 'Bearer', + 'Connection' => 'keep-alive', + 'Keep-Alive' => '30', + 'Splitsdkversion' => "#{config.language}-#{config.version}" + }) + .to_return(status: 200, body: splits) + + fetch_options = { cache_control_headers: false, till: 123_123 } + returned_splits = splits_api.since(-1, fetch_options) + expect(returned_splits[:segment_names]).to eq(Set.new(%w[demo employees])) + + expect(log.string).to include '2 splits retrieved. since=-1' + expect(log.string).to include returned_splits.to_s + end + + it 'returns the splits - checking headers when cache_control_headers is true' do stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') .with(headers: { 'Accept' => '*/*', @@ -60,7 +80,8 @@ }) .to_return(status: 200, body: splits) - returned_splits = splits_api.since(-1, true) + fetch_options = { cache_control_headers: true, till: nil } + returned_splits = splits_api.since(-1, fetch_options) expect(returned_splits[:segment_names]).to eq(Set.new(%w[demo employees])) expect(log.string).to include '2 splits retrieved. since=-1' diff --git a/spec/engine/synchronizer_spec.rb b/spec/engine/synchronizer_spec.rb index 2ad3acfb4..87d4311ae 100644 --- a/spec/engine/synchronizer_spec.rb +++ b/spec/engine/synchronizer_spec.rb @@ -92,15 +92,114 @@ mock_segment_changes('segment1', segment1, '-1') mock_segment_changes('segment1', segment1, '1470947453877') - synchronizer.fetch_splits + synchronizer.fetch_splits(0) expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1')).to have_been_made.once end + it 'fetch_splits - with CDN bypassed' do + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1') + .to_return(status: 200, body: + '{ + "splits": [], + "since": -1, + "till": 1506703262918 + }') + + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918') + .to_return(status: 200, body: + '{ + "splits": [], + "since": 1506703262918, + "till": 1506703262918 + }') + + stub_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918&till=1506703262920') + .to_return(status: 200, body: + '{ + "splits": [], + "since": 1506703262918, + "till": 1506703262921 + }') + + synchronizer.fetch_splits(1_506_703_262_920) + + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918')).to have_been_made.times(9) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918&till=1506703262920')).to have_been_made.once + end + it 'fetch_segment' do - mock_segment_changes('segment3', segment3, '-1') + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=-1') + .to_return(status: 200, body: + '{ + "name": "segment3", + "added": [], + "removed": [], + "since": -1, + "till": 111333 + }') + + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=111333') + .to_return(status: 200, body: + '{ + "name": "segment3", + "added": [], + "removed": [], + "since": 111333, + "till": 111333 + }') + + synchronizer.fetch_segment('segment3', 111_222) + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=111333')).to have_been_made.once + end - synchronizer.fetch_segment('segment3') + it 'fetch_segment - with CDN bypassed' do + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=-1') + .to_return(status: 200, body: + '{ + "name": "segment3", + "added": [], + "removed": [], + "since": -1, + "till": 111333 + }') + + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=111333') + .to_return(status: 200, body: + '{ + "name": "segment3", + "added": [], + "removed": [], + "since": 111333, + "till": 111333 + }') + + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=111333&till=111555') + .to_return(status: 200, body: + '{ + "name": "segment3", + "added": [], + "removed": [], + "since": 111555, + "till": 111555 + }') + + stub_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=111555&till=111555') + .to_return(status: 200, body: + '{ + "name": "segment3", + "added": [], + "removed": [], + "since": 111555, + "till": 111555 + }') + + synchronizer.fetch_segment('segment3', 111_555) expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=-1')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=111333')).to have_been_made.times(10) + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=111333&till=111555')).to have_been_made.once + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment3?since=111555&till=111555')).to have_been_made.once end end diff --git a/spec/splitclient/split_config_spec.rb b/spec/splitclient/split_config_spec.rb index 8cb8ff041..8c5542fa8 100644 --- a/spec/splitclient/split_config_spec.rb +++ b/spec/splitclient/split_config_spec.rb @@ -31,6 +31,8 @@ expect(configs.ip_addresses_enabled).to eq default_ip expect(configs.machine_name).to eq SplitIoClient::SplitConfig.machine_hostname(default_ip, nil, :redis) expect(configs.machine_ip).to eq SplitIoClient::SplitConfig.machine_ip(default_ip, nil, :redis) + expect(configs.on_demand_fetch_retry_delay_seconds).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_retry_delay_seconds + expect(configs.on_demand_fetch_max_retries).to eq SplitIoClient::SplitConfig.default_on_demand_fetch_max_retries end it 'stores and retrieves correctly the customized values' do diff --git a/spec/sse/event_source/back_off_spec.rb b/spec/sse/event_source/back_off_spec.rb index 303a0ac69..b63268c4a 100644 --- a/spec/sse/event_source/back_off_spec.rb +++ b/spec/sse/event_source/back_off_spec.rb @@ -3,13 +3,13 @@ require 'spec_helper' require 'http_server_mock' -describe SplitIoClient::SSE::EventSource::BackOff do - subject { SplitIoClient::SSE::EventSource::BackOff } +describe SplitIoClient::Engine::BackOff do + subject { SplitIoClient::Engine::BackOff } let(:log) { StringIO.new } it 'get intervals and reset attemps' do - back_off = subject.new(1) + back_off = subject.new(1, 0, 5) firts_interval = back_off.interval expect(firts_interval).to eq(0) @@ -27,7 +27,7 @@ it 'with custom config' do streaming_reconnect_back_off_base = 5 - back_off = subject.new(streaming_reconnect_back_off_base) + back_off = subject.new(streaming_reconnect_back_off_base, 0, 30) firts_interval = back_off.interval expect(firts_interval).to eq(0) @@ -42,4 +42,34 @@ reset_interval = back_off.interval expect(reset_interval).to eq(0) end + + it 'with max' do + streaming_reconnect_back_off_base = 5 + back_off = subject.new(streaming_reconnect_back_off_base, 0, 30) + + firts_interval = back_off.interval + expect(firts_interval).to eq(0) + + second_interval = back_off.interval + expect(second_interval).to eq(10) + + third_interval = back_off.interval + expect(third_interval).to eq(20) + + third_interval = back_off.interval + expect(third_interval).to eq(30) + + third_interval = back_off.interval + expect(third_interval).to eq(30) + + third_interval = back_off.interval + expect(third_interval).to eq(30) + + third_interval = back_off.interval + expect(third_interval).to eq(30) + + back_off.reset + reset_interval = back_off.interval + expect(reset_interval).to eq(0) + end end diff --git a/spec/sse/sse_handler_spec.rb b/spec/sse/sse_handler_spec.rb index 75dac8781..f5943138d 100644 --- a/spec/sse/sse_handler_spec.rb +++ b/spec/sse/sse_handler_spec.rb @@ -62,7 +62,7 @@ mock_segment_changes('segment2', segment2, '1470947453878') mock_segment_changes('segment3', segment3, '-1') - synchronizer.fetch_splits + synchronizer.fetch_splits(0) end context 'SPLIT UPDATE event' do @@ -212,7 +212,7 @@ expect(action_event).to eq(SplitIoClient::Constants::PUSH_CONNECTED) expect(sse_handler.sse_client.connected?).to eq(true) - expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=1470947453877')).to have_been_made.times(12) + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=1470947453877')).to have_been_made.times(11) sse_handler.sse_client.close diff --git a/spec/sse/workers/segments_worker_spec.rb b/spec/sse/workers/segments_worker_spec.rb index 7d67f0c91..e0b11ba6c 100644 --- a/spec/sse/workers/segments_worker_spec.rb +++ b/spec/sse/workers/segments_worker_spec.rb @@ -68,7 +68,7 @@ sleep(1) - expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=1470947453877')).to have_been_made.times(12) + expect(a_request(:get, 'https://sdk.split.io/api/segmentChanges/segment1?since=1470947453877')).to have_been_made.times(11) end it 'must not trigger fetch' do diff --git a/spec/sse/workers/splits_worker_spec.rb b/spec/sse/workers/splits_worker_spec.rb index 3274fc41e..bd4991661 100644 --- a/spec/sse/workers/splits_worker_spec.rb +++ b/spec/sse/workers/splits_worker_spec.rb @@ -49,7 +49,8 @@ sleep(1) - expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918')).to have_been_made.times(10) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=-1')).to have_been_made.times(1) + expect(a_request(:get, 'https://sdk.split.io/api/splitChanges?since=1506703262918')).to have_been_made.at_least_times(2) end context 'add change number to queue' do