Skip to content

Commit a00eba2

Browse files
rashidspMichael Ng
authored andcommitted
fix(config-manager): Fixes polling interval and blocking timeout issues (#187)
1 parent efe3a06 commit a00eba2

File tree

4 files changed

+152
-19
lines changed

4 files changed

+152
-19
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ The `sdk_key` is used to compose the outbound HTTP request to the default datafi
9898
You can provide an initial datafile to bootstrap the `DataFileProjectConfig` so that it can be used immediately. The initial datafile also serves as a fallback datafile if HTTP connection cannot be established. The initial datafile will be discarded after the first successful datafile poll.
9999
100100
**polling_interval**
101-
The polling_interval is used to specify a fixed delay in seconds between consecutive HTTP requests for the datafile.
101+
The polling interval is used to specify a fixed delay between consecutive HTTP requests for the datafile. Valid duration is between 1 and 2592000 seconds. Default is 5 minutes.
102102
103103
**url_template**
104104
A string with placeholder `{sdk_key}` can be provided so that this template along with the provided `sdk_key` is used to form the target URL.
@@ -107,7 +107,7 @@ A string with placeholder `{sdk_key}` can be provided so that this template alon
107107
Boolean flag used to start the `AsyncScheduler` for datafile polling if set to `True`.
108108
109109
**blocking_timeout**
110-
Maximum time in seconds to block the `get_config` call until config has been initialized.
110+
The blocking timeout period is used to specify a maximum time to wait for initial bootstrapping. Valid blocking timeout period is between 1 and 2592000 seconds. Default is 15 seconds.
111111
112112
You may also provide your own logger, error handler, or notification center.
113113
@@ -162,11 +162,11 @@ License (MIT): [https://github.com/jnunemaker/httparty/blob/master/MIT-LICENSE](
162162
163163
**JSON Schema Validator** [https://github.com/ruby-json-schema/json-schema](https://github.com/ruby-json-schema/json-schema)
164164
Copyright © 2010-2011, Lookingglass Cyber Solutions
165-
License (MIT): [https://github.com/ruby-json-schema/json-schema/blob/master/LICENSE.md](https://github.com/ruby-json-schema/json-schema/blob/master/LICENSE.md)
165+
License (MIT): [https://github.com/ruby-json-schema/json-schema/blob/master/LICENSE.md](https://github.com/ruby-json-schema/json-schema/blob/master/LICENSE.md)
166166
167167
**Murmurhash3** [https://github.com/funny-falcon/murmurhash3-ruby](https://github.com/funny-falcon/murmurhash3-ruby)
168168
Copyright © 2012 Sokolov Yura 'funny-falcon'
169-
License (MIT): [https://github.com/funny-falcon/murmurhash3-ruby/blob/master/LICENSE](https://github.com/funny-falcon/murmurhash3-ruby/blob/master/LICENSE)
169+
License (MIT): [https://github.com/funny-falcon/murmurhash3-ruby/blob/master/LICENSE](https://github.com/funny-falcon/murmurhash3-ruby/blob/master/LICENSE)
170170
171171
172172
### Additional Code
@@ -198,4 +198,3 @@ License (MIT): [https://github.com/rubocop-hq/rubocop/blob/master/LICENSE.txt](h
198198
**WebMock** [https://github.com/bblimke/webmock](https://github.com/bblimke/webmock)
199199
Copyright © 2009-2010 Bartosz Blimke
200200
License (MIT): [https://github.com/bblimke/webmock/blob/master/LICENSE](https://github.com/bblimke/webmock/blob/master/LICENSE)
201-

lib/optimizely/config_manager/http_project_config_manager.rb

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ def initialize(
6767
@datafile_url = get_datafile_url(sdk_key, url, url_template)
6868
@polling_interval = nil
6969
polling_interval(polling_interval)
70-
blocking_timeout ||= Helpers::Constants::CONFIG_MANAGER['BLOCKING_TIMEOUT']
71-
@blocking_timeout = blocking_timeout
70+
@blocking_timeout = nil
71+
blocking_timeout(blocking_timeout)
7272
@last_modified = nil
7373
@async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
7474
@async_scheduler.start! if start_by_default == true
@@ -185,18 +185,73 @@ def polling_interval(polling_interval)
185185
#
186186
# polling_interval - Time in seconds after which to update datafile.
187187

188-
# If polling interval is less than minimum allowed interval then set it to default update interval.
188+
# If valid set given polling interval, default update interval otherwise.
189189

190-
if (polling_interval.is_a? Integer) && (polling_interval >= Helpers::Constants::CONFIG_MANAGER['MIN_UPDATE_INTERVAL'])
191-
@polling_interval = polling_interval
190+
if polling_interval.nil?
191+
@logger.log(
192+
Logger::DEBUG,
193+
"Polling interval is not provided. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
194+
)
195+
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
192196
return
193197
end
194198

195-
@logger.log(
196-
Logger::DEBUG,
197-
"Invalid update_interval #{polling_interval} provided. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']}"
198-
)
199-
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
199+
unless polling_interval.is_a? Integer
200+
@logger.log(
201+
Logger::ERROR,
202+
"Polling interval '#{polling_interval}' has invalid type. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
203+
)
204+
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
205+
return
206+
end
207+
208+
unless polling_interval.between?(Helpers::Constants::CONFIG_MANAGER['MIN_SECONDS_LIMIT'], Helpers::Constants::CONFIG_MANAGER['MAX_SECONDS_LIMIT'])
209+
@logger.log(
210+
Logger::DEBUG,
211+
"Polling interval '#{polling_interval}' has invalid range. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']} seconds."
212+
)
213+
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_UPDATE_INTERVAL']
214+
return
215+
end
216+
217+
@polling_interval = polling_interval
218+
end
219+
220+
def blocking_timeout(blocking_timeout)
221+
# Sets time in seconds to block the get_config call until config has been initialized.
222+
#
223+
# blocking_timeout - Time in seconds after which to update datafile.
224+
225+
# If valid set given timeout, default blocking_timeout otherwise.
226+
227+
if blocking_timeout.nil?
228+
@logger.log(
229+
Logger::DEBUG,
230+
"Blocking timeout is not provided. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
231+
)
232+
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
233+
return
234+
end
235+
236+
unless blocking_timeout.is_a? Integer
237+
@logger.log(
238+
Logger::ERROR,
239+
"Blocking timeout '#{blocking_timeout}' has invalid type. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
240+
)
241+
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
242+
return
243+
end
244+
245+
unless blocking_timeout.between?(Helpers::Constants::CONFIG_MANAGER['MIN_SECONDS_LIMIT'], Helpers::Constants::CONFIG_MANAGER['MAX_SECONDS_LIMIT'])
246+
@logger.log(
247+
Logger::DEBUG,
248+
"Blocking timeout '#{blocking_timeout}' has invalid range. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
249+
)
250+
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
251+
return
252+
end
253+
254+
@blocking_timeout = blocking_timeout
200255
end
201256

202257
def get_datafile_url(sdk_key, url, url_template)

lib/optimizely/helpers/constants.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,13 +361,15 @@ module Constants
361361
}.freeze
362362

363363
CONFIG_MANAGER = {
364-
# Maximum time in seconds to block the get_config call until config has been initialized.
365-
'BLOCKING_TIMEOUT' => 15,
366364
'DATAFILE_URL_TEMPLATE' => 'https://cdn.optimizely.com/datafiles/%s.json',
365+
# Default time in seconds to block the get_config call until config has been initialized.
366+
'DEFAULT_BLOCKING_TIMEOUT' => 15,
367367
# Default config update interval of 5 minutes
368368
'DEFAULT_UPDATE_INTERVAL' => 5 * 60,
369-
# Minimum config update interval of 1 second
370-
'MIN_UPDATE_INTERVAL' => 1,
369+
# Maximum update interval or blocking timeout: 30 days
370+
'MAX_SECONDS_LIMIT' => 2_592_000,
371+
# Minimum update interval or blocking timeout: 1 second
372+
'MIN_SECONDS_LIMIT' => 1,
371373
# Time in seconds before which request for datafile times out
372374
'REQUEST_TIMEOUT' => 10
373375
}.freeze

spec/config_manager/http_project_config_manager_spec.rb

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@
224224
sdk_key: 'QBw9gFM8oTn7ogY9ANCC1z',
225225
datafile: config_body_JSON,
226226
polling_interval: 1,
227+
blocking_timeout: 2,
227228
notification_center: notification_center,
228229
logger: spy_logger
229230
)
@@ -291,4 +292,80 @@
291292
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, 'Must provide at least one of sdk_key or url.')
292293
end
293294
end
295+
296+
describe '.polling_interval' do
297+
it 'should log an error when polling_interval is nil' do
298+
Optimizely::HTTPProjectConfigManager.new(
299+
sdk_key: 'sdk_key',
300+
url: nil,
301+
polling_interval: nil,
302+
blocking_timeout: 5,
303+
error_handler: error_handler,
304+
logger: spy_logger
305+
)
306+
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, 'Polling interval is not provided. Defaulting to 300 seconds.')
307+
end
308+
309+
it 'should log an error when polling_interval has invalid type' do
310+
Optimizely::HTTPProjectConfigManager.new(
311+
sdk_key: 'sdk_key',
312+
url: nil,
313+
polling_interval: true,
314+
blocking_timeout: 5,
315+
error_handler: error_handler,
316+
logger: spy_logger
317+
)
318+
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Polling interval 'true' has invalid type. Defaulting to 300 seconds.")
319+
end
320+
321+
it 'should log an error when polling_interval has invalid range' do
322+
Optimizely::HTTPProjectConfigManager.new(
323+
sdk_key: 'sdk_key',
324+
url: nil,
325+
polling_interval: 999_999_999_999_999_999,
326+
blocking_timeout: 5,
327+
error_handler: error_handler,
328+
logger: spy_logger
329+
)
330+
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, "Polling interval '999999999999999999' has invalid range. Defaulting to 300 seconds.")
331+
end
332+
end
333+
334+
describe '.blocking_timeout' do
335+
it 'should log an error when blocking_timeout is nil' do
336+
Optimizely::HTTPProjectConfigManager.new(
337+
sdk_key: 'sdk_key',
338+
url: nil,
339+
polling_interval: 5,
340+
blocking_timeout: nil,
341+
error_handler: error_handler,
342+
logger: spy_logger
343+
)
344+
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, 'Blocking timeout is not provided. Defaulting to 15 seconds.')
345+
end
346+
347+
it 'should log an error when blocking_timeout has invalid type' do
348+
Optimizely::HTTPProjectConfigManager.new(
349+
sdk_key: 'sdk_key',
350+
url: nil,
351+
polling_interval: 5,
352+
blocking_timeout: true,
353+
error_handler: error_handler,
354+
logger: spy_logger
355+
)
356+
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Blocking timeout 'true' has invalid type. Defaulting to 15 seconds.")
357+
end
358+
359+
it 'should log an error when blocking_timeout has invalid range' do
360+
Optimizely::HTTPProjectConfigManager.new(
361+
sdk_key: 'sdk_key',
362+
url: nil,
363+
polling_interval: 5,
364+
blocking_timeout: 999_999_999_999_999_999,
365+
error_handler: error_handler,
366+
logger: spy_logger
367+
)
368+
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, "Blocking timeout '999999999999999999' has invalid range. Defaulting to 15 seconds.")
369+
end
370+
end
294371
end

0 commit comments

Comments
 (0)