optimizely-sdk 3.3.1 → 3.3.2.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|