ff-ruby-server-sdk 1.4.3 → 1.4.5

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
2
  SHA256:
3
- metadata.gz: 21ede6e2de94f3ed2aec871c40b8dbee7a9feec6adbdec14726e5237caedbb0b
4
- data.tar.gz: e89c0b57ae84666c87dccf6b25a67f0d0ca77990220ee014c9abe1a1225993c9
3
+ metadata.gz: a4ab2e35a2f4c9b26b92808b6bd3c8bc1b91dd7be7449fa1ed42324dc7a5dd8c
4
+ data.tar.gz: d426cc3eadd246b1db5fa67ec583fb4f5c38591195579b780687ad5582262e5e
5
5
  SHA512:
6
- metadata.gz: 943c5b49c1bf887dd6ff24edce3e03cd2bff1badde8dd0802fdc8bda042e2e260b2cd7929c18189ec9dfb39f8b8189bec5b49e32ab021c722a17da71268bcccb
7
- data.tar.gz: 5f32b626e8c49fd3074401db1ef6810acfb79b4cdba6f03e7f55e31a2a6e921b73ce273d71780f30dc90f18e74f691e4a203da6503bb0a473a60e49b0b56d37b
6
+ metadata.gz: 970504a5a04c27c883b12efc6a54cec3486c14789c3a49389a33dad1d8f43fa0f6fb26046628f397368c4cfa9eb1bd6eca91fb28b72062cd7cec8f0c69ca487a
7
+ data.tar.gz: d4fc7208a07a8fec31990f693358b649c5b8e63f23f9b95c605b3321d6d45e9d7a5934b262fcd248c06d3322de450eb3a3686bac63ebc8bf426e18fba2f32cf5
@@ -91,6 +91,20 @@ class Evaluator < Evaluation
91
91
 
92
92
  flag = @repository.get_flag(identifier)
93
93
 
94
+ # Check if flag exists
95
+ if flag.nil?
96
+ # Log a warning if the flag is not found
97
+ @logger.warn "Flag not found for identifier '#{identifier}'. Serving default variation."
98
+ return nil
99
+ end
100
+
101
+ # Check if the flag's kind matches the expected type
102
+ unless flag.kind == expected
103
+ @logger.warn "Flag kind mismatch: expected '#{expected}', but got '#{flag.kind}' for identifier '#{identifier}'. Serving default variation."
104
+ return nil
105
+ end
106
+
107
+ # Proceed with prerequisite check if flag type is as expected
94
108
  if flag != nil && flag.kind == expected
95
109
  unless flag.prerequisites.empty?
96
110
  pre_req = check_pre_requisite(flag, target)
@@ -103,12 +117,15 @@ class Evaluator < Evaluation
103
117
 
104
118
  if variation != nil
105
119
  if callback != nil
106
- callback.process_evaluation(flag, target, variation)
120
+ feature_name = flag.feature
121
+ variation_identifier = variation.identifier
122
+ callback.process_evaluation(feature_name, target, variation_identifier)
107
123
  end
108
124
  return variation
109
125
  end
110
126
  end
111
127
 
128
+ # Returning nil will indicate to callers to serve the default variation
112
129
  nil
113
130
  end
114
131
 
@@ -50,6 +50,7 @@ class InnerClient < ClientCallback
50
50
 
51
51
  @connector = connector
52
52
  end
53
+ @condition = ConditionVariable.new
53
54
 
54
55
  @closing = false
55
56
  @failure = false
@@ -66,22 +67,34 @@ class InnerClient < ClientCallback
66
67
  end
67
68
 
68
69
  def bool_variation(identifier, target, default_value)
69
-
70
+ unless @initialized
71
+ log_sdk_not_initialized_warning(identifier, default_value)
72
+ return default_value
73
+ end
70
74
  @evaluator.bool_variation(identifier, target, default_value, @evaluator_callback)
71
75
  end
72
76
 
73
77
  def string_variation(identifier, target, default_value)
74
-
78
+ unless @initialized
79
+ log_sdk_not_initialized_warning(identifier, default_value)
80
+ return default_value
81
+ end
75
82
  @evaluator.string_variation(identifier, target, default_value, @evaluator_callback)
76
83
  end
77
84
 
78
85
  def number_variation(identifier, target, default_value)
79
-
86
+ unless @initialized
87
+ log_sdk_not_initialized_warning(identifier, default_value)
88
+ return default_value
89
+ end
80
90
  @evaluator.number_variation(identifier, target, default_value, @evaluator_callback)
81
91
  end
82
92
 
83
93
  def json_variation(identifier, target, default_value)
84
-
94
+ unless @initialized
95
+ log_sdk_not_initialized_warning(identifier, default_value)
96
+ return default_value
97
+ end
85
98
  @evaluator.json_variation(identifier, target, default_value, @evaluator_callback)
86
99
  end
87
100
 
@@ -109,6 +122,7 @@ class InnerClient < ClientCallback
109
122
  def on_auth_failed
110
123
  SdkCodes::warn_auth_failed_srv_defaults @config.logger
111
124
  @initialized = true
125
+ @condition.signal
112
126
  end
113
127
 
114
128
  def close
@@ -195,67 +209,74 @@ class InnerClient < ClientCallback
195
209
  end
196
210
 
197
211
  def on_processor_ready(processor)
212
+ @my_mutex.synchronize do
198
213
 
199
- if @closing
214
+ if @closing
200
215
 
201
- return
202
- end
216
+ return
217
+ end
203
218
 
204
- if processor == @poll_processor
219
+ if processor == @poll_processor
205
220
 
206
- @poller_ready = true
207
- @config.logger.debug "PollingProcessor ready"
208
- end
221
+ @poller_ready = true
222
+ @config.logger.debug "PollingProcessor ready"
223
+ end
209
224
 
210
- if processor == @update_processor
225
+ if processor == @update_processor
211
226
 
212
- @stream_ready = true
213
- @config.logger.debug "Updater ready"
214
- end
227
+ @stream_ready = true
228
+ @config.logger.debug "Updater ready"
229
+ end
215
230
 
216
- if processor == @metrics_processor
231
+ if processor == @metrics_processor
217
232
 
218
- @metrics_ready = true
219
- @config.logger.debug "Metrics ready"
220
- end
233
+ @metrics_ready = true
234
+ @config.logger.debug "Metrics ready"
235
+ end
221
236
 
222
- if (@config.stream_enabled && !@stream_ready) ||
223
- (@config.analytics_enabled && !@metrics_ready) ||
224
- !@poller_ready
237
+ if (@config.stream_enabled && !@stream_ready) ||
238
+ (@config.analytics_enabled && !@metrics_ready) ||
239
+ !@poller_ready
225
240
 
226
- return
227
- end
241
+ return
242
+ end
228
243
 
229
- SdkCodes.info_sdk_init_ok @config.logger
244
+ SdkCodes.info_sdk_init_ok @config.logger
230
245
 
231
- @initialized = true
246
+ @condition.signal
247
+ @initialized = true
248
+ end
232
249
  end
233
250
 
234
251
  def wait_for_initialization(timeout: nil)
235
- synchronize do
236
- SdkCodes::info_sdk_waiting_to_initialize(@config.logger, timeout)
252
+ SdkCodes::info_sdk_waiting_to_initialize(@config.logger, timeout)
253
+ return if @initialized
237
254
 
255
+ @my_mutex.synchronize do
238
256
  start_time = Time.now
257
+ remaining = timeout ? timeout / 1000.0 : nil # Convert timeout to seconds
239
258
 
240
259
  until @initialized
241
- # Check if a timeout is specified and has been exceeded
242
- if timeout && (Time.now - start_time) > (timeout / 1000.0)
243
- @config.logger.warn "The SDK has timed out waiting to initialize with supplied timeout #{timeout} ms"
244
- handle_initialization_failure
245
- end
246
260
 
247
- sleep(1)
248
- end
261
+ # Break if timeout has elapsed
262
+ if remaining && remaining <= 0
263
+ @config.logger.warn "The SDK has timed out waiting to initialize with supplied timeout #{timeout} ms. The SDK will continue to initialize in the background. Default variations will be served until the SDK initializes."
264
+ break
265
+ end
266
+ # Wait for the signal or timeout
267
+ @condition.wait(@my_mutex, remaining)
249
268
 
250
- if @failure
251
- raise "Initialization failed"
269
+ # Recalculate the remaining time after the wait
270
+ if timeout
271
+ elapsed = Time.now - start_time
272
+ remaining = (timeout / 1000.0) - elapsed
273
+ end
252
274
  end
253
275
 
254
- @config.logger.debug "Waiting for initialization has completed"
276
+ @config.logger.debug "Waiting for initialization has completed" if @initialized
255
277
  end
256
278
  end
257
279
 
258
-
259
280
  protected
260
281
 
261
282
  def handle_initialization_failure
@@ -316,9 +337,8 @@ class InnerClient < ClientCallback
316
337
 
317
338
  private
318
339
 
319
- def synchronize(&block)
320
-
321
- @my_mutex.synchronize(&block)
340
+ def log_sdk_not_initialized_warning(identifier, default_value)
341
+ @config.logger.warn "SDKCODE:6001: SDK is not initialized; serving default variation for bool variation: identifier=#{identifier}, default=#{default_value}"
322
342
  end
323
343
 
324
344
  end
@@ -21,10 +21,10 @@ class InnerClientFlagEvaluateCallback < FlagEvaluateCallback
21
21
  @metrics_processor = metrics_processor
22
22
  end
23
23
 
24
- def process_evaluation(feature_config, target, variation)
24
+ def process_evaluation(feature_name, target, variation_identifier)
25
25
 
26
- @logger.debug "Processing evaluation: #{feature_config&.feature || 'nil feature'}, #{target&.identifier || 'nil target'}"
26
+ @logger.debug "Processing evaluation: #{feature_name || 'nil feature'}, #{variation_identifier || 'nil variation'}, #{target&.identifier || 'nil target'}"
27
27
 
28
- @metrics_processor.register_evaluation(target, feature_config, variation)
28
+ @metrics_processor.register_evaluation(target, feature_name, variation_identifier)
29
29
  end
30
30
  end
@@ -1,50 +1,14 @@
1
1
  require "time"
2
- require "concurrent-ruby"
2
+ require 'thread'
3
+ require "set"
3
4
 
4
5
  require_relative "../dto/target"
5
6
  require_relative "../../sdk/version"
6
7
  require_relative "../common/closeable"
7
8
  require_relative "../common/sdk_codes"
8
- require_relative "../api/metrics_event"
9
9
  require_relative "../api/summary_metrics"
10
10
 
11
11
  class MetricsProcessor < Closeable
12
- GLOBAL_TARGET = Target.new(identifier: "__global__cf_target", name: "Global Target").freeze
13
-
14
- class FrequencyMap < Concurrent::Map
15
- def initialize(options = nil, &block)
16
- super
17
- end
18
-
19
- def increment(key)
20
- compute(key) do |old_value|
21
- if old_value == nil;
22
- 1
23
- else
24
- old_value + 1
25
- end
26
- end
27
- end
28
-
29
- def get(key)
30
- self[key]
31
- end
32
-
33
- # TODO Will be removed in V2 in favour of simplified clearing. Currently not used outside of tests.
34
- def drain_to_map
35
- result = {}
36
- each_key do |key|
37
- result[key] = 0
38
- end
39
- result.each_key do |key|
40
- value = get_and_set(key, 0)
41
- result[key] = value
42
- delete_pair(key, 0)
43
- end
44
- result
45
- end
46
- end
47
-
48
12
  def init(connector, config, callback)
49
13
 
50
14
  unless connector.kind_of?(Connector)
@@ -76,15 +40,20 @@ class MetricsProcessor < Closeable
76
40
  @feature_name_attribute = "featureName"
77
41
  @variation_identifier_attribute = "variationIdentifier"
78
42
 
79
- @executor = Concurrent::FixedThreadPool.new(10)
80
-
81
- # Used for locking the evalution and target metrics maps before we clone them
43
+ # Evaluation and target metrics
82
44
  @metric_maps_mutex = Mutex.new
83
- @evaluation_metrics = FrequencyMap.new
84
- @target_metrics = Concurrent::Map.new
45
+ @evaluation_metrics = {}
46
+ @target_metrics = {}
85
47
 
86
- # Keep track of targets that have already been sent to avoid sending them again
87
- @seen_targets = Concurrent::Map.new
48
+ # Keep track of targets that have already been sent to avoid sending them again. We track a max 500K targets
49
+ # to prevent unbounded growth.
50
+ @seen_targets_mutex = Mutex.new
51
+ @seen_targets = Set.new
52
+ @max_seen_targets = 500000
53
+ @seen_targets_full = false
54
+
55
+ # Mutex to protect aggregation and sending metrics at the end of an interval
56
+ @send_data_mutex = Mutex.new
88
57
 
89
58
  @callback.on_metrics_ready
90
59
  end
@@ -107,8 +76,8 @@ class MetricsProcessor < Closeable
107
76
  @config.logger.debug "Closing metrics processor"
108
77
  end
109
78
 
110
- def register_evaluation(target, feature_config, variation)
111
- register_evaluation_metric(feature_config, variation)
79
+ def register_evaluation(target, feature_name, variation_identifier)
80
+ register_evaluation_metric(feature_name, variation_identifier)
112
81
  if target
113
82
  register_target_metric(target)
114
83
  end
@@ -116,128 +85,129 @@ class MetricsProcessor < Closeable
116
85
 
117
86
  private
118
87
 
119
- def register_evaluation_metric(feature_config, variation)
88
+ def register_evaluation_metric(feature_name, variation_identifier)
120
89
  # Guard clause to ensure feature_config, @global_target, and variation are valid.
121
- # While they should be, this adds protection for an edge case we are seeing with very large
122
- # project sizes. Issue being tracked in FFM-12192, and once resolved, can feasibly remove
123
- # these checks in a future release.
124
- if feature_config.nil? || !feature_config.respond_to?(:feature) || feature_config.feature.nil?
125
- @config.logger.warn("Skipping invalid MetricsEvent: feature_config is missing or incomplete. feature_config=#{feature_config.inspect}")
90
+ # While they should be, this adds protection for an edge case we are seeing where the the ConcurrentMap (now replaced with our own thread safe hash)
91
+ # seemed to be accessing invalid areas of memory and seg faulting.
92
+ # Issue being tracked in FFM-12192, and once resolved, can remove these checks in a future release && once the issue is resolved.
93
+ unless feature_name && !feature_name.empty?
94
+ @config.logger.warn("Skipping invalid MetricsEvent: feature_config is missing or incomplete. feature_config=#{feature_name.inspect}")
126
95
  return
127
96
  end
128
97
 
129
- if GLOBAL_TARGET.nil? || !GLOBAL_TARGET.respond_to?(:identifier) || GLOBAL_TARGET.identifier.nil?
130
- @config.logger.warn("Skipping invalid MetricsEvent: global_target is missing or incomplete. global_target=#{GLOBAL_TARGET.inspect}")
98
+ unless variation_identifier && !variation_identifier.empty?
99
+ @config.logger.warn("Skipping iInvalid MetricsEvent: variation is missing or incomplete. variation=#{variation_identifier.inspect}")
131
100
  return
132
101
  end
133
102
 
134
- if variation.nil? || !variation.respond_to?(:identifier) || variation.identifier.nil?
135
- @config.logger.warn("Skipping iInvalid MetricsEvent: variation is missing or incomplete. variation=#{variation.inspect}")
136
- return
137
- end
138
-
139
- event = MetricsEvent.new(feature_config, GLOBAL_TARGET, variation, @config.logger)
140
103
  @metric_maps_mutex.synchronize do
141
- @evaluation_metrics.increment event
104
+ key = "#{feature_name}\0#{variation_identifier}"
105
+ @evaluation_metrics[key] = (@evaluation_metrics[key] || 0) + 1
142
106
  end
143
107
  end
144
108
 
145
109
  def register_target_metric(target)
146
- if target.is_private
147
- return
148
- end
149
-
150
- already_seen = @seen_targets.put_if_absent(target.identifier, true)
151
-
152
- if already_seen
153
- return
110
+ return if target.is_private
111
+
112
+ add_to_target_metrics = @seen_targets_mutex.synchronize do
113
+ # If the set is full, directly allow adding to target_metrics
114
+ if @seen_targets_full
115
+ true
116
+ elsif @seen_targets.include?(target.identifier)
117
+ false
118
+ else
119
+ @seen_targets.add(target.identifier)
120
+ @seen_targets_full = @seen_targets.size >= @max_seen_targets
121
+ true
122
+ end
154
123
  end
155
124
 
125
+ # Add to target_metrics if marked for inclusion
156
126
  @metric_maps_mutex.synchronize do
157
- @target_metrics.put(target.identifier, target)
158
- end
127
+ @target_metrics[target.identifier] = target
128
+ end if add_to_target_metrics
159
129
  end
160
130
 
131
+
161
132
  def run_one_iteration
162
133
  send_data_and_reset_cache(@evaluation_metrics, @target_metrics)
163
134
  end
164
135
 
165
136
  def send_data_and_reset_cache(evaluation_metrics_map, target_metrics_map)
166
- # A single lock is used to synchronise access to both the evaluation and target metrics maps.
167
- # While separate locks could be applied to each map individually, we want an interval's eval/target
168
- # metrics to be processed in an atomic unit.
169
- evaluation_metrics_map_clone, target_metrics_map_clone = @metric_maps_mutex.synchronize do
170
-
171
- clone_evaluations = Concurrent::Map.new
172
- clone_targets = Concurrent::Map.new
173
- # Clone and clear evaluation metrics map
174
- evaluation_metrics_map.each_pair do |key, value|
175
- clone_evaluations[key] = value
176
- end
177
-
178
- evaluation_metrics_map.clear
137
+ @send_data_mutex.synchronize do
138
+ begin
179
139
 
180
- target_metrics_map.each_pair do |key, value|
181
- clone_targets[key] = value
182
- end
140
+ evaluation_metrics_map_clone, target_metrics_map_clone = @metric_maps_mutex.synchronize do
141
+ # Check if we have metrics to send; if not, skip sending metrics
142
+ if evaluation_metrics_map.empty? && target_metrics_map.empty?
143
+ @config.logger.debug "No metrics to send. Skipping sending metrics this interval"
144
+ return
145
+ end
183
146
 
184
- target_metrics_map.clear
147
+ # Deep clone the evaluation metrics
148
+ cloned_evaluations = Marshal.load(Marshal.dump(evaluation_metrics_map)).freeze
149
+ evaluation_metrics_map.clear
185
150
 
186
- [clone_evaluations, clone_targets]
151
+ # Deep clone the target metrics
152
+ cloned_targets = Marshal.load(Marshal.dump(target_metrics_map)).freeze
153
+ target_metrics_map.clear
154
+ [cloned_evaluations, cloned_targets]
187
155
 
188
- end
156
+ end
189
157
 
190
- metrics = prepare_summary_metrics_body(evaluation_metrics_map_clone, target_metrics_map_clone)
158
+ metrics = prepare_summary_metrics_body(evaluation_metrics_map_clone, target_metrics_map_clone)
191
159
 
192
- unless metrics.metrics_data.empty?
193
- start_time = (Time.now.to_f * 1000).to_i
194
- @connector.post_metrics(metrics)
195
- end_time = (Time.now.to_f * 1000).to_i
196
- if end_time - start_time > @config.metrics_service_acceptable_duration
197
- @config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
160
+ unless metrics.metrics_data.empty?
161
+ start_time = (Time.now.to_f * 1000).to_i
162
+ @connector.post_metrics(metrics)
163
+ end_time = (Time.now.to_f * 1000).to_i
164
+ if end_time - start_time > @config.metrics_service_acceptable_duration
165
+ @config.logger.debug "Metrics service API duration=[" + (end_time - start_time).to_s + "]"
166
+ end
167
+ end
168
+ rescue => e
169
+ @config.logger.warn "Error when preparing and sending metrics: #{e.message}"
170
+ @config.logger.warn e.backtrace&.join("\n") || "No backtrace available"
198
171
  end
199
172
  end
200
173
  end
201
174
 
202
- def prepare_summary_metrics_body(evaluation_metrics_map, target_metrics_map)
175
+ def prepare_summary_metrics_body(evaluation_metrics_clone, target_metrics_clone)
203
176
  metrics = OpenapiClient::Metrics.new({ :target_data => [], :metrics_data => [] })
204
177
 
205
178
  total_count = 0
206
- evaluation_metrics_map.each do |key, value|
207
- # While Components should not be missing, this adds protection for an edge case we are seeing with very large
208
- # project sizes. Issue being tracked in FFM-12192, and once resolved, can feasibly remove
209
- # these checks in a future release.
210
- # Initialize an array to collect missing components
211
- missing_components = []
212
-
213
- # Check each required component and add to missing_components if absent
214
- missing_components << 'feature_config' unless key.respond_to?(:feature_config) && key.feature_config
215
- missing_components << 'variation' unless key.respond_to?(:variation) && key.variation
216
- missing_components << 'target' unless key.respond_to?(:target) && key.target
217
- missing_components << 'count' if value.nil?
218
-
219
- # If any components are missing, log a detailed warning and skip processing
220
- unless missing_components.empty?
221
- @config.logger.warn "Skipping invalid metrics event: missing #{missing_components.join(', ')} in key: #{key.inspect}, full details: #{key.inspect}"
179
+ evaluation_metrics_clone.each do |key, value|
180
+ feature_name, variation_identifier = key.split("\0", 2)
181
+
182
+ # Although feature_name and variation_identifier should always be present,
183
+ # this guard provides protection against an edge case where keys reference
184
+ # other objects in memory. In versions <= 1.4.4, we were keying on the MetricsEvent
185
+ # class (now deleted). To remediate this, we have transitioned to using strings as keys.
186
+ # This issue is being tracked in FFM-12192. Once resolved, these checks can be safely
187
+ # removed in a future release.
188
+ # If any required data is missing, log a detailed warning and skip processing.
189
+ unless feature_name && variation_identifier && value.is_a?(Integer) && value > 0
190
+ @config.logger.warn "Skipping invalid metrics event: missing or invalid feature_name, variation_identifier, or count. Key: #{key.inspect}, Count: #{value.inspect}"
222
191
  next
223
192
  end
224
193
 
225
194
  total_count += value
195
+
226
196
  metrics_data = OpenapiClient::MetricsData.new({ :attributes => [] })
227
197
  metrics_data.timestamp = (Time.now.to_f * 1000).to_i
228
198
  metrics_data.count = value
229
199
  metrics_data.metrics_type = "FFMETRICS"
230
- metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => key.feature_config.feature }))
231
- metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => key.variation.identifier }))
200
+ metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @feature_name_attribute, :value => feature_name }))
201
+ metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @variation_identifier_attribute, :value => variation_identifier }))
232
202
  metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @target_attribute, :value => @global_target_identifier }))
233
203
  metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_type, :value => @server }))
234
204
  metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_language, :value => "ruby" }))
235
205
  metrics_data.attributes.push(OpenapiClient::KeyValue.new({ :key => @sdk_version, :value => @jar_version }))
236
206
  metrics.metrics_data.push(metrics_data)
237
207
  end
238
- @config.logger.debug "Pushed #{total_count} metric evaluations to server. metrics_data count is #{evaluation_metrics_map.size}. target_data count is #{target_metrics_map.size}"
208
+ @config.logger.debug "Pushed #{total_count} metric evaluations to server. metrics_data count is #{evaluation_metrics_clone.size}. target_data count is #{target_metrics_clone.size}"
239
209
 
240
- target_metrics_map.each_pair do |_, value|
210
+ target_metrics_clone.each_pair do |_, value|
241
211
  add_target_data(metrics, value)
242
212
  end
243
213
 
@@ -276,18 +246,27 @@ class MetricsProcessor < Closeable
276
246
  @running = true
277
247
  @thread = Thread.new do
278
248
  @config.logger.debug "Async started: " + self.to_s
279
- while @ready do
249
+ mutex = Mutex.new
250
+ condition = ConditionVariable.new
251
+
252
+ while @ready
280
253
  unless @initialized
281
254
  @initialized = true
282
- SdkCodes::info_metrics_thread_started @config.logger
255
+ SdkCodes::info_metrics_thread_started(@config.logger)
256
+ end
257
+
258
+ mutex.synchronize do
259
+ # Wait for the specified interval or until notified
260
+ condition.wait(mutex, @config.frequency)
283
261
  end
284
- sleep(@config.frequency)
285
- run_one_iteration
262
+
263
+ # Re-check @ready before running the iteration
264
+ run_one_iteration if @ready
286
265
  end
287
266
  end
288
- @thread.run
289
267
  end
290
268
 
269
+
291
270
  def stop_async
292
271
  @ready = false
293
272
  @initialized = false
@@ -15,7 +15,7 @@ class UpdateProcessor < Closeable
15
15
  @connector = connector
16
16
  @repository = repository
17
17
  @updater = callback
18
- @executor = Concurrent::FixedThreadPool.new(100)
18
+ @executor = Concurrent::FixedThreadPool.new(20)
19
19
 
20
20
  if logger != nil
21
21
 
@@ -5,7 +5,7 @@ module Ff
5
5
  module Server
6
6
  module Sdk
7
7
 
8
- VERSION = "1.4.3"
8
+ VERSION = "1.4.5"
9
9
  end
10
10
  end
11
11
  end
@@ -17,7 +17,6 @@ require_relative "sdk/api/cf_client"
17
17
  require_relative "sdk/api/evaluation"
18
18
  require_relative "sdk/api/inner_client"
19
19
  require_relative "sdk/api/auth_service"
20
- require_relative "sdk/api/metrics_event"
21
20
  require_relative "sdk/api/default_cache"
22
21
  require_relative "sdk/api/config_builder"
23
22
  require_relative "sdk/api/file_map_store"
data/scripts/sdk_specs.sh CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/bin/bash
2
2
 
3
3
  export ff_ruby_sdk="ff-ruby-server-sdk"
4
- export ff_ruby_sdk_version="1.4.3"
4
+ export ff_ruby_sdk_version="1.4.5"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ff-ruby-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.3
4
+ version: 1.4.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - 'Miloš Vasić, cyr.: Милош Васић'
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-11-13 00:00:00.000000000 Z
11
+ date: 2024-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -142,14 +142,14 @@ dependencies:
142
142
  requirements:
143
143
  - - '='
144
144
  - !ruby/object:Gem::Version
145
- version: 0.1.6
145
+ version: 0.1.7
146
146
  type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - '='
151
151
  - !ruby/object:Gem::Version
152
- version: 0.1.6
152
+ version: 0.1.7
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: typhoeus
155
155
  requirement: !ruby/object:Gem::Requirement
@@ -253,7 +253,6 @@ files:
253
253
  - lib/ff/ruby/server/sdk/api/inner_client_repository_callback.rb
254
254
  - lib/ff/ruby/server/sdk/api/inner_client_updater.rb
255
255
  - lib/ff/ruby/server/sdk/api/metrics_callback.rb
256
- - lib/ff/ruby/server/sdk/api/metrics_event.rb
257
256
  - lib/ff/ruby/server/sdk/api/metrics_processor.rb
258
257
  - lib/ff/ruby/server/sdk/api/operators.rb
259
258
  - lib/ff/ruby/server/sdk/api/polling_processor.rb
@@ -1,38 +0,0 @@
1
- class MetricsEvent
2
-
3
- attr_accessor :feature_config, :target, :variation
4
-
5
- def initialize(feature_config, target, variation, logger)
6
-
7
- @target = target
8
- @variation = variation
9
- @feature_config = feature_config
10
- @logger = logger
11
- freeze
12
- end
13
-
14
- def ==(other)
15
- eql?(other)
16
- end
17
-
18
- def eql?(other)
19
- # Guard clause other is the same type.
20
- # While it should be, this adds protection for an edge case we are seeing with very large
21
- # project sizes. Issue being tracked in FFM-12192, and once resolved, can feasibly remove
22
- # these checks in a future release.
23
- unless other.is_a?(MetricsEvent)
24
- @logger.warn("Warning: Attempted to compare MetricsEvent with #{other.class.name}" )
25
- return false
26
- end
27
-
28
- feature_config.feature == other.feature_config.feature and
29
- variation.identifier == other.variation.identifier and
30
- target.identifier == other.target.identifier
31
- end
32
-
33
- def hash
34
- feature_config.feature.hash | variation.identifier.hash | target.identifier.hash
35
- end
36
-
37
-
38
- end