optimizely-sdk 3.3.1 → 3.3.2.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/optimizely.rb +2 -2
- data/lib/optimizely/config_manager/async_scheduler.rb +2 -2
- data/lib/optimizely/config_manager/http_project_config_manager.rb +5 -3
- data/lib/optimizely/event/batch_event_processor.rb +40 -39
- data/lib/optimizely/event_dispatcher.rb +38 -14
- data/lib/optimizely/exceptions.rb +7 -0
- data/lib/optimizely/notification_center.rb +13 -5
- data/lib/optimizely/version.rb +1 -1
- metadata +5 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4b941180fbc22ae8f59eff475967cb6f130c4845e2a47324c3e18252b4a35685
|
4
|
+
data.tar.gz: 560e8ce82a288fb98c6d088264b0df6ff1cd4b0d3aba162d72ab1541a9fe76ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 66dbbf2dcbfe72ba85201b552f5d8a1e10c33eb109c18e51a56e46582754fcc21ad86d7d0ce487e032e872e135c2671269451c788447c527d8a2b8498fd1421e
|
7
|
+
data.tar.gz: 3ca3f728b5c3c20355e192f26a50913194cebf4fe3a446cddd94a72daecc9c0f50d74af7c36863cfcde5dd02e93baea2d14fb0137a6b0e5e3481d135ff323ae0
|
data/lib/optimizely.rb
CHANGED
@@ -70,7 +70,7 @@ module Optimizely
|
|
70
70
|
)
|
71
71
|
@logger = logger || NoOpLogger.new
|
72
72
|
@error_handler = error_handler || NoOpErrorHandler.new
|
73
|
-
@event_dispatcher = event_dispatcher || EventDispatcher.new
|
73
|
+
@event_dispatcher = event_dispatcher || EventDispatcher.new(logger: @logger, error_handler: @error_handler)
|
74
74
|
@user_profile_service = user_profile_service
|
75
75
|
|
76
76
|
begin
|
@@ -701,7 +701,7 @@ module Optimizely
|
|
701
701
|
|
702
702
|
return if Helpers::Validator.event_dispatcher_valid?(@event_dispatcher)
|
703
703
|
|
704
|
-
@event_dispatcher = EventDispatcher.new
|
704
|
+
@event_dispatcher = EventDispatcher.new(logger: @logger, error_handler: @error_handler)
|
705
705
|
raise InvalidInputError, 'event_dispatcher'
|
706
706
|
end
|
707
707
|
|
@@ -75,10 +75,10 @@ module Optimizely
|
|
75
75
|
loop do
|
76
76
|
begin
|
77
77
|
callback.call
|
78
|
-
rescue
|
78
|
+
rescue StandardError => e
|
79
79
|
@logger.log(
|
80
80
|
Logger::ERROR,
|
81
|
-
|
81
|
+
"Something went wrong when executing passed callback. #{e.message}"
|
82
82
|
)
|
83
83
|
stop!
|
84
84
|
end
|
@@ -70,14 +70,16 @@ module Optimizely
|
|
70
70
|
@blocking_timeout = nil
|
71
71
|
blocking_timeout(blocking_timeout)
|
72
72
|
@last_modified = nil
|
73
|
-
@async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
|
74
|
-
@async_scheduler.start! if start_by_default == true
|
75
|
-
@stopped = false
|
76
73
|
@skip_json_validation = skip_json_validation
|
77
74
|
@notification_center = notification_center.is_a?(Optimizely::NotificationCenter) ? notification_center : NotificationCenter.new(@logger, @error_handler)
|
78
75
|
@config = datafile.nil? ? nil : DatafileProjectConfig.create(datafile, @logger, @error_handler, @skip_json_validation)
|
79
76
|
@mutex = Mutex.new
|
80
77
|
@resource = ConditionVariable.new
|
78
|
+
@async_scheduler = AsyncScheduler.new(method(:fetch_datafile_config), @polling_interval, auto_update, @logger)
|
79
|
+
# Start async scheduler in the end to avoid race condition where scheduler executes
|
80
|
+
# callback which makes use of variables not yet initialized by the main thread.
|
81
|
+
@async_scheduler.start! if start_by_default == true
|
82
|
+
@stopped = false
|
81
83
|
end
|
82
84
|
|
83
85
|
def ready?
|
@@ -20,7 +20,7 @@ require_relative '../helpers/validator'
|
|
20
20
|
module Optimizely
|
21
21
|
class BatchEventProcessor < EventProcessor
|
22
22
|
# BatchEventProcessor is a batched implementation of the Interface EventProcessor.
|
23
|
-
# Events passed to the BatchEventProcessor are immediately added to
|
23
|
+
# Events passed to the BatchEventProcessor are immediately added to an EventQueue.
|
24
24
|
# The BatchEventProcessor maintains a single consumer thread that pulls events off of
|
25
25
|
# the BlockingQueue and buffers them for either a configured batch size or for a
|
26
26
|
# maximum duration before the resulting LogEvent is sent to the NotificationCenter.
|
@@ -30,13 +30,15 @@ module Optimizely
|
|
30
30
|
DEFAULT_BATCH_SIZE = 10
|
31
31
|
DEFAULT_BATCH_INTERVAL = 30_000 # interval in milliseconds
|
32
32
|
DEFAULT_QUEUE_CAPACITY = 1000
|
33
|
+
DEFAULT_TIMEOUT_INTERVAL = 5 # interval in seconds
|
34
|
+
MAX_NIL_COUNT = 3
|
33
35
|
|
34
36
|
FLUSH_SIGNAL = 'FLUSH_SIGNAL'
|
35
37
|
SHUTDOWN_SIGNAL = 'SHUTDOWN_SIGNAL'
|
36
38
|
|
37
39
|
def initialize(
|
38
40
|
event_queue: SizedQueue.new(DEFAULT_QUEUE_CAPACITY),
|
39
|
-
event_dispatcher:
|
41
|
+
event_dispatcher: nil,
|
40
42
|
batch_size: DEFAULT_BATCH_SIZE,
|
41
43
|
flush_interval: DEFAULT_BATCH_INTERVAL,
|
42
44
|
logger: NoOpLogger.new,
|
@@ -44,7 +46,7 @@ module Optimizely
|
|
44
46
|
)
|
45
47
|
@event_queue = event_queue
|
46
48
|
@logger = logger
|
47
|
-
@event_dispatcher = event_dispatcher
|
49
|
+
@event_dispatcher = event_dispatcher || EventDispatcher.new(logger: @logger)
|
48
50
|
@batch_size = if (batch_size.is_a? Integer) && positive_number?(batch_size)
|
49
51
|
batch_size
|
50
52
|
else
|
@@ -58,8 +60,6 @@ module Optimizely
|
|
58
60
|
DEFAULT_BATCH_INTERVAL
|
59
61
|
end
|
60
62
|
@notification_center = notification_center
|
61
|
-
@mutex = Mutex.new
|
62
|
-
@received = ConditionVariable.new
|
63
63
|
@current_batch = []
|
64
64
|
@started = false
|
65
65
|
start!
|
@@ -71,15 +71,13 @@ module Optimizely
|
|
71
71
|
return
|
72
72
|
end
|
73
73
|
@flushing_interval_deadline = Helpers::DateTimeUtils.create_timestamp + @flush_interval
|
74
|
+
@logger.log(Logger::INFO, 'Starting scheduler.')
|
74
75
|
@thread = Thread.new { run }
|
75
76
|
@started = true
|
76
77
|
end
|
77
78
|
|
78
79
|
def flush
|
79
|
-
@
|
80
|
-
@event_queue << FLUSH_SIGNAL
|
81
|
-
@received.signal
|
82
|
-
end
|
80
|
+
@event_queue << FLUSH_SIGNAL
|
83
81
|
end
|
84
82
|
|
85
83
|
def process(user_event)
|
@@ -90,56 +88,54 @@ module Optimizely
|
|
90
88
|
return
|
91
89
|
end
|
92
90
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
@logger.log(Logger::WARN, 'Payload not accepted by the queue.')
|
99
|
-
return
|
100
|
-
end
|
91
|
+
begin
|
92
|
+
@event_queue.push(user_event, true)
|
93
|
+
rescue Exception
|
94
|
+
@logger.log(Logger::WARN, 'Payload not accepted by the queue.')
|
95
|
+
return
|
101
96
|
end
|
102
97
|
end
|
103
98
|
|
104
99
|
def stop!
|
105
100
|
return unless @started
|
106
101
|
|
107
|
-
@
|
108
|
-
|
109
|
-
|
110
|
-
end
|
111
|
-
|
102
|
+
@logger.log(Logger::INFO, 'Stopping scheduler.')
|
103
|
+
@event_queue << SHUTDOWN_SIGNAL
|
104
|
+
@thread.join(DEFAULT_TIMEOUT_INTERVAL)
|
112
105
|
@started = false
|
113
|
-
@logger.log(Logger::WARN, 'Stopping scheduler.')
|
114
|
-
@thread.exit
|
115
106
|
end
|
116
107
|
|
117
108
|
private
|
118
109
|
|
119
110
|
def run
|
111
|
+
# if we receive a number of item nils that reach MAX_NIL_COUNT,
|
112
|
+
# then we hang on the pop via setting use_pop to false
|
113
|
+
@nil_count = 0
|
114
|
+
# hang on pop if true
|
115
|
+
@use_pop = false
|
120
116
|
loop do
|
121
|
-
if Helpers::DateTimeUtils.create_timestamp
|
122
|
-
@logger.log(
|
123
|
-
Logger::DEBUG,
|
124
|
-
'Deadline exceeded flushing current batch.'
|
125
|
-
)
|
117
|
+
if Helpers::DateTimeUtils.create_timestamp >= @flushing_interval_deadline
|
118
|
+
@logger.log(Logger::DEBUG, 'Deadline exceeded flushing current batch.')
|
126
119
|
flush_queue!
|
120
|
+
@flushing_interval_deadline = Helpers::DateTimeUtils.create_timestamp + @flush_interval
|
121
|
+
@use_pop = true if @nil_count > MAX_NIL_COUNT
|
127
122
|
end
|
128
123
|
|
129
|
-
item =
|
130
|
-
|
131
|
-
@mutex.synchronize do
|
132
|
-
@received.wait(@mutex, 0.05)
|
133
|
-
item = @event_queue.pop if @event_queue.length.positive?
|
134
|
-
end
|
124
|
+
item = @event_queue.pop if @event_queue.length.positive? || @use_pop
|
135
125
|
|
136
126
|
if item.nil?
|
137
|
-
|
127
|
+
# when nil count is greater than MAX_NIL_COUNT, we hang on the pop until there is an item available.
|
128
|
+
# this avoids to much spinning of the loop.
|
129
|
+
@nil_count += 1
|
138
130
|
next
|
139
131
|
end
|
140
132
|
|
133
|
+
# reset nil_count and use_pop if we have received an item.
|
134
|
+
@nil_count = 0
|
135
|
+
@use_pop = false
|
136
|
+
|
141
137
|
if item == SHUTDOWN_SIGNAL
|
142
|
-
@logger.log(Logger::
|
138
|
+
@logger.log(Logger::DEBUG, 'Received shutdown signal.')
|
143
139
|
break
|
144
140
|
end
|
145
141
|
|
@@ -152,7 +148,7 @@ module Optimizely
|
|
152
148
|
add_to_batch(item) if item.is_a? Optimizely::UserEvent
|
153
149
|
end
|
154
150
|
rescue SignalException
|
155
|
-
@logger.log(Logger::
|
151
|
+
@logger.log(Logger::ERROR, 'Interrupted while processing buffer.')
|
156
152
|
rescue Exception => e
|
157
153
|
@logger.log(Logger::ERROR, "Uncaught exception processing buffer. #{e.message}")
|
158
154
|
ensure
|
@@ -168,6 +164,11 @@ module Optimizely
|
|
168
164
|
|
169
165
|
log_event = Optimizely::EventFactory.create_log_event(@current_batch, @logger)
|
170
166
|
begin
|
167
|
+
@logger.log(
|
168
|
+
Logger::INFO,
|
169
|
+
'Flushing Queue.'
|
170
|
+
)
|
171
|
+
|
171
172
|
@event_dispatcher.dispatch_event(log_event)
|
172
173
|
@notification_center&.send_notifications(
|
173
174
|
NotificationCenter::NOTIFICATION_TYPES[:LOG_EVENT],
|
@@ -192,7 +193,7 @@ module Optimizely
|
|
192
193
|
@current_batch << user_event
|
193
194
|
return unless @current_batch.length >= @batch_size
|
194
195
|
|
195
|
-
@logger.log(Logger::DEBUG, 'Flushing on max batch size
|
196
|
+
@logger.log(Logger::DEBUG, 'Flushing on max batch size.')
|
196
197
|
flush_queue!
|
197
198
|
end
|
198
199
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#
|
4
|
-
# Copyright 2016-2017, Optimizely and contributors
|
4
|
+
# Copyright 2016-2017, 2019, Optimizely and contributors
|
5
5
|
#
|
6
6
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
7
|
# you may not use this file except in compliance with the License.
|
@@ -15,6 +15,8 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
#
|
18
|
+
require_relative 'exceptions'
|
19
|
+
|
18
20
|
require 'httparty'
|
19
21
|
|
20
22
|
module Optimizely
|
@@ -28,26 +30,48 @@ module Optimizely
|
|
28
30
|
# @api constants
|
29
31
|
REQUEST_TIMEOUT = 10
|
30
32
|
|
33
|
+
def initialize(logger: nil, error_handler: nil)
|
34
|
+
@logger = logger || NoOpLogger.new
|
35
|
+
@error_handler = error_handler || NoOpErrorHandler.new
|
36
|
+
end
|
37
|
+
|
31
38
|
# Dispatch the event being represented by the Event object.
|
32
39
|
#
|
33
40
|
# @param event - Event object
|
34
41
|
def dispatch_event(event)
|
35
42
|
if event.http_verb == :get
|
36
|
-
|
37
|
-
|
38
|
-
rescue Timeout::Error => e
|
39
|
-
return e
|
40
|
-
end
|
43
|
+
response = HTTParty.get(event.url, headers: event.headers, query: event.params, timeout: REQUEST_TIMEOUT)
|
44
|
+
|
41
45
|
elsif event.http_verb == :post
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
timeout: REQUEST_TIMEOUT)
|
47
|
-
rescue Timeout::Error => e
|
48
|
-
return e
|
49
|
-
end
|
46
|
+
response = HTTParty.post(event.url,
|
47
|
+
body: event.params.to_json,
|
48
|
+
headers: event.headers,
|
49
|
+
timeout: REQUEST_TIMEOUT)
|
50
50
|
end
|
51
|
+
|
52
|
+
error_msg = "Event failed to dispatch with response code: #{response.code}"
|
53
|
+
|
54
|
+
case response.code
|
55
|
+
when 400...500
|
56
|
+
@logger.log(Logger::ERROR, error_msg)
|
57
|
+
@error_handler.handle_error(HTTPCallError.new("HTTP Client Error: #{response.code}"))
|
58
|
+
|
59
|
+
when 500...600
|
60
|
+
@logger.log(Logger::ERROR, error_msg)
|
61
|
+
@error_handler.handle_error(HTTPCallError.new("HTTP Server Error: #{response.code}"))
|
62
|
+
else
|
63
|
+
@logger.log(Logger::DEBUG, 'event successfully sent with response code ' + response.code.to_s)
|
64
|
+
end
|
65
|
+
rescue Timeout::Error => e
|
66
|
+
@logger.log(Logger::ERROR, "Request Timed out. Error: #{e}")
|
67
|
+
@error_handler.handle_error(e)
|
68
|
+
|
69
|
+
# Returning Timeout error to retain existing behavior.
|
70
|
+
e
|
71
|
+
rescue StandardError => e
|
72
|
+
@logger.log(Logger::ERROR, "Event failed to dispatch. Error: #{e}")
|
73
|
+
@error_handler.handle_error(e)
|
74
|
+
nil
|
51
75
|
end
|
52
76
|
end
|
53
77
|
end
|
@@ -18,6 +18,13 @@
|
|
18
18
|
module Optimizely
|
19
19
|
class Error < StandardError; end
|
20
20
|
|
21
|
+
class HTTPCallError < Error
|
22
|
+
# Raised when a 4xx or 5xx response code is recieved.
|
23
|
+
def initialize(msg = 'HTTP call resulted in a response with an error code.')
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
21
28
|
class InvalidAudienceError < Error
|
22
29
|
# Raised when an invalid audience is provided
|
23
30
|
|
@@ -39,20 +39,28 @@ module Optimizely
|
|
39
39
|
|
40
40
|
# Adds notification callback to the notification center
|
41
41
|
#
|
42
|
-
# @param notification_type -
|
43
|
-
# @param notification_callback -
|
42
|
+
# @param notification_type - One of the constants in NOTIFICATION_TYPES
|
43
|
+
# @param notification_callback [lambda, Method, Callable] (default: block) - Called when the event is sent
|
44
|
+
# @yield Block to be used as callback if callback omitted.
|
44
45
|
#
|
45
46
|
# @return [notification ID] Used to remove the notification
|
46
47
|
|
47
|
-
def add_notification_listener(notification_type, notification_callback)
|
48
|
+
def add_notification_listener(notification_type, notification_callback = nil, &block)
|
48
49
|
return nil unless notification_type_valid?(notification_type)
|
49
50
|
|
51
|
+
if notification_callback && block_given?
|
52
|
+
@logger.log Logger::ERROR, 'Callback and block are mutually exclusive.'
|
53
|
+
return nil
|
54
|
+
end
|
55
|
+
|
56
|
+
notification_callback ||= block
|
57
|
+
|
50
58
|
unless notification_callback
|
51
59
|
@logger.log Logger::ERROR, 'Callback can not be empty.'
|
52
60
|
return nil
|
53
61
|
end
|
54
62
|
|
55
|
-
unless notification_callback.
|
63
|
+
unless notification_callback.respond_to? :call
|
56
64
|
@logger.log Logger::ERROR, 'Invalid notification callback given.'
|
57
65
|
return nil
|
58
66
|
end
|
@@ -70,7 +78,7 @@ module Optimizely
|
|
70
78
|
#
|
71
79
|
# @param notification_id
|
72
80
|
#
|
73
|
-
# @return [Boolean]
|
81
|
+
# @return [Boolean] true if found and removed, false otherwise
|
74
82
|
|
75
83
|
def remove_notification_listener(notification_id)
|
76
84
|
unless notification_id
|
data/lib/optimizely/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: optimizely-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.3.
|
4
|
+
version: 3.3.2.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Optimizely
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-12-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -202,12 +202,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
202
202
|
version: '0'
|
203
203
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
204
204
|
requirements:
|
205
|
-
- - "
|
205
|
+
- - ">"
|
206
206
|
- !ruby/object:Gem::Version
|
207
|
-
version:
|
207
|
+
version: 1.3.1
|
208
208
|
requirements: []
|
209
209
|
rubyforge_project:
|
210
|
-
rubygems_version: 2.
|
210
|
+
rubygems_version: 2.7.6.2
|
211
211
|
signing_key:
|
212
212
|
specification_version: 4
|
213
213
|
summary: Ruby SDK for Optimizely's testing framework
|