Skip to content

Commit 2d0b8b3

Browse files
rashidspmjc1283
authored andcommitted
fix(blocking-api): fixed issue related to default blocking-timeout and blocking API methods. (#202)
Summary: - Fixed bug when `blocking_timeout` is set to default. - Block all APIs for the `blocking_timeout` period or datafile is not loaded successfully. - Updated doc string.
1 parent 7ab4650 commit 2d0b8b3

File tree

8 files changed

+119
-104
lines changed

8 files changed

+119
-104
lines changed

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ You can initialize the Optimizely instance in two ways: directly with a datafile
4444
4545
When the `datafile` is given then it will be used initially before any update.
4646
47-
2. Initialize Optimizely by providing a Config Manager that implements a 'get_config' method. You can customize our `HTTPConfigManager` as needed.
47+
2. Initialize Optimizely by providing a Config Manager that implements a `config` method. You can customize our `HTTPConfigManager` as needed.
4848
4949
```
5050
custom_config_manager = CustomConfigManager.new
@@ -124,7 +124,7 @@ The following properties can be set to override the default configurations for `
124124
| datafile | nil | Initial datafile, typically sourced from a local cached source
125125
| auto_update | true | Boolean flag to specify if callback needs to execute infinitely or only once
126126
| start_by_default | true | Boolean flag to specify if datafile polling should start right away as soon as `HTTPConfigManager` initializes
127-
| blocking_timeout | 15 seconds | Maximum time in seconds to block the `get_config` call until config has been initialized
127+
| blocking_timeout | 15 seconds | Maximum time in seconds to block the `config` call until config has been initialized
128128
129129
A notification signal will be triggered whenever a _new_ datafile is fetched and Project Config is updated. To subscribe to these notifications, use the `notification_center.add_notification_listener(Optimizely::NotificationCenter::NOTIFICATION_TYPES[:OPTIMIZELY_CONFIG_UPDATE], @callback)`
130130

lib/optimizely.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class Project
5252
# @param skip_json_validation - Optional boolean param to skip JSON schema validation of the provided datafile.
5353
# @params sdk_key - Optional string uniquely identifying the datafile corresponding to project and environment combination.
5454
# Must provide at least one of datafile or sdk_key.
55-
# @param config_manager - Optional Responds to get_config.
55+
# @param config_manager - Optional Responds to 'config' method.
5656
# @param notification_center - Optional Instance of NotificationCenter.
5757
# @param event_processor - Optional Responds to process.
5858

@@ -82,7 +82,7 @@ def initialize(
8282

8383
@notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
8484

85-
@config_manager = if config_manager.respond_to?(:get_config)
85+
@config_manager = if config_manager.respond_to?(:config)
8686
config_manager
8787
elsif sdk_key
8888
HTTPProjectConfigManager.new(

lib/optimizely/config_manager/http_project_config_manager.rb

+20-17
Original file line numberDiff line numberDiff line change
@@ -30,17 +30,17 @@ module Optimizely
3030
class HTTPProjectConfigManager < ProjectConfigManager
3131
# Config manager that polls for the datafile and updated ProjectConfig based on an update interval.
3232

33-
attr_reader :config, :closed
33+
attr_reader :stopped
3434

3535
# Initialize config manager. One of sdk_key or url has to be set to be able to use.
3636
#
3737
# sdk_key - Optional string uniquely identifying the datafile. It's required unless a URL is passed in.
3838
# datafile: Optional JSON string representing the project.
3939
# polling_interval - Optional floating point number representing time interval in seconds
4040
# at which to request datafile and set ProjectConfig.
41-
# blocking_timeout -
42-
# auto_update -
43-
# start_by_default -
41+
# blocking_timeout - Optional Time in seconds to block the config call until config object has been initialized.
42+
# auto_update - Boolean indicates to run infinitely or only once.
43+
# start_by_default - Boolean indicates to start by default AsyncScheduler.
4444
# url - Optional string representing URL from where to fetch the datafile. If set it supersedes the sdk_key.
4545
# url_template - Optional string template which in conjunction with sdk_key
4646
# determines URL from where to fetch the datafile.
@@ -72,7 +72,7 @@ def initialize(
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
75-
@closed = false
75+
@stopped = false
7676
@skip_json_validation = skip_json_validation
7777
@notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
7878
@config = datafile.nil? ? nil : DatafileProjectConfig.create(datafile, @logger, @error_handler, @skip_json_validation)
@@ -85,33 +85,36 @@ def ready?
8585
end
8686

8787
def start!
88-
if @closed
89-
@logger.log(Logger::WARN, 'Not starting. Already closed.')
88+
if @stopped
89+
@logger.log(Logger::WARN, 'Not starting. Already stopped.')
9090
return
9191
end
9292

9393
@async_scheduler.start!
94-
@closed = false
94+
@stopped = false
9595
end
9696

9797
def stop!
98-
if @closed
98+
if @stopped
9999
@logger.log(Logger::WARN, 'Not pausing. Manager has not been started.')
100100
return
101101
end
102102

103103
@async_scheduler.stop!
104104
@config = nil
105-
@closed = true
105+
@stopped = true
106106
end
107107

108-
def get_config
108+
def config
109109
# Get Project Config.
110110

111+
# if stopped is true, then simply return @config.
111112
# If the background datafile polling thread is running. and config has been initalized,
112-
# we simply return config.
113+
# we simply return @config.
113114
# If it is not, we wait and block maximum for @blocking_timeout.
114115
# If thread is not running, we fetch the datafile and update config.
116+
return @config if @stopped
117+
115118
if @async_scheduler.running
116119
return @config if ready?
117120

@@ -232,9 +235,9 @@ def polling_interval(polling_interval)
232235
end
233236

234237
def blocking_timeout(blocking_timeout)
235-
# Sets time in seconds to block the get_config call until config has been initialized.
238+
# Sets time in seconds to block the config call until config has been initialized.
236239
#
237-
# blocking_timeout - Time in seconds after which to update datafile.
240+
# blocking_timeout - Time in seconds to block the config call.
238241

239242
# If valid set given timeout, default blocking_timeout otherwise.
240243

@@ -243,7 +246,7 @@ def blocking_timeout(blocking_timeout)
243246
Logger::DEBUG,
244247
"Blocking timeout is not provided. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
245248
)
246-
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
249+
@blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
247250
return
248251
end
249252

@@ -252,7 +255,7 @@ def blocking_timeout(blocking_timeout)
252255
Logger::ERROR,
253256
"Blocking timeout '#{blocking_timeout}' has invalid type. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
254257
)
255-
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
258+
@blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
256259
return
257260
end
258261

@@ -261,7 +264,7 @@ def blocking_timeout(blocking_timeout)
261264
Logger::DEBUG,
262265
"Blocking timeout '#{blocking_timeout}' has invalid range. Defaulting to #{Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']} seconds."
263266
)
264-
@polling_interval = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
267+
@blocking_timeout = Helpers::Constants::CONFIG_MANAGER['DEFAULT_BLOCKING_TIMEOUT']
265268
return
266269
end
267270

lib/optimizely/helpers/constants.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ module Constants
362362

363363
CONFIG_MANAGER = {
364364
'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.
365+
# Default time in seconds to block the 'config' method call until 'config' instance has been initialized.
366366
'DEFAULT_BLOCKING_TIMEOUT' => 15,
367367
# Default config update interval of 5 minutes
368368
'DEFAULT_UPDATE_INTERVAL' => 5 * 60,

lib/optimizely/optimizely_factory.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def self.default_instance(sdk_key, datafile = nil)
7777

7878
# Returns a new optimizely instance.
7979
#
80-
# @param config_manager - Required ConfigManagerInterface Responds to get_config.
80+
# @param config_manager - Required ConfigManagerInterface Responds to 'config' method.
8181
def self.default_instance_with_config_manager(config_manager)
8282
Optimizely::Project.new(nil, nil, nil, nil, nil, nil, nil, config_manager)
8383
end
@@ -92,7 +92,7 @@ def self.default_instance_with_config_manager(config_manager)
9292
# By default all exceptions will be suppressed.
9393
# @param skip_json_validation - Optional Boolean param to skip JSON schema validation of the provided datafile.
9494
# @param user_profile_service - Optional UserProfileServiceInterface Provides methods to store and retreive user profiles.
95-
# @param config_manager - Optional ConfigManagerInterface Responds to get_config.
95+
# @param config_manager - Optional ConfigManagerInterface Responds to 'config' method.
9696
# @param notification_center - Optional Instance of NotificationCenter.
9797
#
9898
# if @max_event_batch_size and @max_event_flush_interval are nil then default batchsize and flush_interval

spec/config_manager/http_project_config_manager_spec.rb

+37-25
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
)
3737

3838
until http_project_config_manager.ready?; end
39-
expect(http_project_config_manager.get_config).not_to eq(nil)
39+
expect(http_project_config_manager.config).not_to eq(nil)
4040
end
4141

4242
it 'should get project config when sdk_key is given' do
@@ -45,7 +45,7 @@
4545
)
4646

4747
until http_project_config_manager.ready?; end
48-
expect(http_project_config_manager.get_config).not_to eq(nil)
48+
expect(http_project_config_manager.config).not_to eq(nil)
4949
end
5050

5151
it 'should get project config when sdk_key and valid url_template is given' do
@@ -55,7 +55,7 @@
5555
)
5656
until http_project_config_manager.ready?; end
5757

58-
expect(http_project_config_manager.get_config).not_to eq(nil)
58+
expect(http_project_config_manager.config).not_to eq(nil)
5959
end
6060

6161
it 'should get instance ready immediately when datafile is provided' do
@@ -80,7 +80,7 @@
8080
until http_project_config_manager.ready?; end
8181
finish = Time.now
8282
expect(finish - start).to be > 0
83-
expect(http_project_config_manager.get_config).not_to eq(nil)
83+
expect(http_project_config_manager.config).not_to eq(nil)
8484
end
8585

8686
it 'should send config update notification when project config is updated' do
@@ -96,7 +96,7 @@
9696
)
9797

9898
until http_project_config_manager.ready?; end
99-
expect(http_project_config_manager.get_config).not_to eq(nil)
99+
expect(http_project_config_manager.config).not_to eq(nil)
100100
end
101101

102102
it 'should not send config update notification when datafile is provided' do
@@ -129,8 +129,8 @@
129129
http_project_config_manager.start!
130130

131131
# All instance variables values of http_project_config_manager
132-
http_project_config_manager_arr = http_project_config_manager.get_config.instance_variables.map do |attr|
133-
http_project_config_manager.get_config.instance_variable_get attr
132+
http_project_config_manager_arr = http_project_config_manager.config.instance_variables.map do |attr|
133+
http_project_config_manager.config.instance_variable_get attr
134134
end
135135

136136
# All instance variables values of datafile_project_config
@@ -149,7 +149,7 @@
149149
auto_update: false,
150150
start_by_default: false
151151
)
152-
expect(http_project_config_manager.get_config).not_to eq(nil)
152+
expect(http_project_config_manager.config).not_to eq(nil)
153153
finish = Time.now
154154
expect(finish - start).to be < 1
155155
end
@@ -181,7 +181,7 @@
181181
'Old revision number: 42. New revision number: 81.').once
182182

183183
# Asserts that config has updated from URL.
184-
expect(http_project_config_manager.get_config.account_id).not_to eql(datafile_project_config.account_id)
184+
expect(http_project_config_manager.config.account_id).not_to eql(datafile_project_config.account_id)
185185
end
186186
end
187187

@@ -195,7 +195,7 @@
195195
error_handler: error_handler
196196
)
197197

198-
expect(http_project_config_manager.get_config).not_to eq(nil)
198+
expect(http_project_config_manager.config).not_to eq(nil)
199199
end
200200

201201
it 'should get instance ready immediately' do
@@ -236,7 +236,7 @@
236236
expect(spy_logger).to have_received(:log).with(Logger::DEBUG, 'Received new datafile and updated config. ' \
237237
'Old revision number: 42. New revision number: 81.').once
238238

239-
expect(http_project_config_manager.get_config.account_id).not_to eql(datafile_project_config.account_id)
239+
expect(http_project_config_manager.config.account_id).not_to eql(datafile_project_config.account_id)
240240
end
241241
end
242242

@@ -246,7 +246,7 @@
246246
sdk_key: 'QBw9gFM8oTn7ogY9ANCC1z'
247247
)
248248

249-
expect(http_project_config_manager.get_config).not_to eq(nil)
249+
expect(http_project_config_manager.config).not_to eq(nil)
250250
end
251251

252252
it 'should fetch datafile url and get instance ready' do
@@ -275,7 +275,7 @@
275275
blocking_timeout: 5
276276
)
277277
http_project_config_manager.start!
278-
expect(http_project_config_manager.get_config).to eq(nil)
278+
expect(http_project_config_manager.config).to eq(nil)
279279
end
280280
end
281281

@@ -294,77 +294,89 @@
294294
end
295295

296296
describe '.polling_interval' do
297-
it 'should log an error when polling_interval is nil' do
298-
Optimizely::HTTPProjectConfigManager.new(
297+
it 'should set default and log an error when polling_interval is nil' do
298+
http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
299299
sdk_key: 'sdk_key',
300300
url: nil,
301301
polling_interval: nil,
302302
blocking_timeout: 5,
303303
error_handler: error_handler,
304304
logger: spy_logger
305305
)
306+
307+
expect(http_project_config_manager.instance_variable_get(:@polling_interval)).to eq(300)
306308
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, 'Polling interval is not provided. Defaulting to 300 seconds.')
307309
end
308310

309-
it 'should log an error when polling_interval has invalid type' do
310-
Optimizely::HTTPProjectConfigManager.new(
311+
it 'should set default and log an error when polling_interval has invalid type' do
312+
http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
311313
sdk_key: 'sdk_key',
312314
url: nil,
313315
polling_interval: true,
314316
blocking_timeout: 5,
315317
error_handler: error_handler,
316318
logger: spy_logger
317319
)
320+
321+
expect(http_project_config_manager.instance_variable_get(:@polling_interval)).to eq(300)
318322
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Polling interval 'true' has invalid type. Defaulting to 300 seconds.")
319323
end
320324

321-
it 'should log an error when polling_interval has invalid range' do
322-
Optimizely::HTTPProjectConfigManager.new(
325+
it 'should set default and log an error when polling_interval has invalid range' do
326+
http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
323327
sdk_key: 'sdk_key',
324328
url: nil,
325329
polling_interval: 999_999_999_999_999_999,
326330
blocking_timeout: 5,
327331
error_handler: error_handler,
328332
logger: spy_logger
329333
)
334+
335+
expect(http_project_config_manager.instance_variable_get(:@polling_interval)).to eq(300)
330336
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, "Polling interval '999999999999999999' has invalid range. Defaulting to 300 seconds.")
331337
end
332338
end
333339

334340
describe '.blocking_timeout' do
335-
it 'should log an error when blocking_timeout is nil' do
336-
Optimizely::HTTPProjectConfigManager.new(
341+
it 'should set default and log an error when blocking_timeout is nil' do
342+
http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
337343
sdk_key: 'sdk_key',
338344
url: nil,
339345
polling_interval: 5,
340346
blocking_timeout: nil,
341347
error_handler: error_handler,
342348
logger: spy_logger
343349
)
350+
351+
expect(http_project_config_manager.instance_variable_get(:@blocking_timeout)).to eq(15)
344352
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, 'Blocking timeout is not provided. Defaulting to 15 seconds.')
345353
end
346354

347-
it 'should log an error when blocking_timeout has invalid type' do
348-
Optimizely::HTTPProjectConfigManager.new(
355+
it 'should set default and log an error when blocking_timeout has invalid type' do
356+
http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
349357
sdk_key: 'sdk_key',
350358
url: nil,
351359
polling_interval: 5,
352360
blocking_timeout: true,
353361
error_handler: error_handler,
354362
logger: spy_logger
355363
)
364+
365+
expect(http_project_config_manager.instance_variable_get(:@blocking_timeout)).to eq(15)
356366
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Blocking timeout 'true' has invalid type. Defaulting to 15 seconds.")
357367
end
358368

359-
it 'should log an error when blocking_timeout has invalid range' do
360-
Optimizely::HTTPProjectConfigManager.new(
369+
it 'should set default and log an error when blocking_timeout has invalid range' do
370+
http_project_config_manager = Optimizely::HTTPProjectConfigManager.new(
361371
sdk_key: 'sdk_key',
362372
url: nil,
363373
polling_interval: 5,
364374
blocking_timeout: 999_999_999_999_999_999,
365375
error_handler: error_handler,
366376
logger: spy_logger
367377
)
378+
379+
expect(http_project_config_manager.instance_variable_get(:@blocking_timeout)).to eq(15)
368380
expect(spy_logger).to have_received(:log).once.with(Logger::DEBUG, "Blocking timeout '999999999999999999' has invalid range. Defaulting to 15 seconds.")
369381
end
370382
end

spec/optimizely_factory_spec.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
describe '.default_instance_with_manager' do
5353
it 'should take provided custom config manager' do
5454
class CustomConfigManager
55-
def get_config; end
55+
attr_reader :config
5656
end
5757

5858
custom_config_manager = CustomConfigManager.new

0 commit comments

Comments
 (0)