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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0eefcbf5e58e06f6e28c1437d3ed456810a4240f
4
- data.tar.gz: c50c1633875300966b654e1fc0b2d246a386a089
2
+ SHA256:
3
+ metadata.gz: 4b941180fbc22ae8f59eff475967cb6f130c4845e2a47324c3e18252b4a35685
4
+ data.tar.gz: 560e8ce82a288fb98c6d088264b0df6ff1cd4b0d3aba162d72ab1541a9fe76ee
5
5
  SHA512:
6
- metadata.gz: 43c28f74959363c09232e85422cf7995a22163f4a8166300a87379bc05f29e49cae4d0af3cf8f3203b74bd02674d74b34be950b2cc307de0be3accf41371ae29
7
- data.tar.gz: 2d73a7d2a6909f5a1a90287faa5af11579de08a072d5522feb6a7aec54bc3af758a752c6a217642518d4e6bd7eed035b46e186eae8e8bcc9d42e0c98a5d7bc31
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
- 'Something went wrong when running passed function.'
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 a EventQueue.
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: Optimizely::EventDispatcher.new,
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
- @mutex.synchronize do
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
- @mutex.synchronize do
94
- begin
95
- @event_queue << user_event
96
- @received.signal
97
- rescue Exception
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
- @mutex.synchronize do
108
- @event_queue << SHUTDOWN_SIGNAL
109
- @received.signal
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 > @flushing_interval_deadline
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 = nil
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
- sleep(0.05)
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::INFO, 'Received shutdown signal.')
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::INFO, 'Interrupted while processing buffer.')
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
- begin
37
- HTTParty.get(event.url, headers: event.headers, query: event.params, timeout: REQUEST_TIMEOUT)
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
- begin
43
- HTTParty.post(event.url,
44
- body: event.params.to_json,
45
- headers: event.headers,
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 - One of the constants in NOTIFICATION_TYPES
43
- # @param notification_callback - Function to call when the event is sent
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.is_a? Method
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] The function returns true if found and removed, false otherwise
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
@@ -17,5 +17,5 @@
17
17
  #
18
18
  module Optimizely
19
19
  CLIENT_ENGINE = 'ruby-sdk'
20
- VERSION = '3.3.1'
20
+ VERSION = '3.3.2.rc1'
21
21
  end
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.1
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-10-10 00:00:00.000000000 Z
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: '0'
207
+ version: 1.3.1
208
208
  requirements: []
209
209
  rubyforge_project:
210
- rubygems_version: 2.5.1
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