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 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