launchdarkly-server-sdk 6.2.5 → 7.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -2
- data/lib/ldclient-rb/config.rb +203 -43
- data/lib/ldclient-rb/context.rb +487 -0
- data/lib/ldclient-rb/evaluation_detail.rb +85 -26
- data/lib/ldclient-rb/events.rb +185 -146
- data/lib/ldclient-rb/flags_state.rb +25 -14
- data/lib/ldclient-rb/impl/big_segments.rb +117 -0
- data/lib/ldclient-rb/impl/context.rb +96 -0
- data/lib/ldclient-rb/impl/context_filter.rb +145 -0
- data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
- data/lib/ldclient-rb/impl/evaluator.rb +428 -132
- data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
- data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
- data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
- data/lib/ldclient-rb/impl/event_sender.rb +6 -6
- data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
- data/lib/ldclient-rb/impl/event_types.rb +78 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +7 -7
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +92 -28
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +212 -0
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +165 -32
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +40 -0
- data/lib/ldclient-rb/impl/model/clause.rb +39 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +213 -0
- data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
- data/lib/ldclient-rb/impl/model/segment.rb +126 -0
- data/lib/ldclient-rb/impl/model/serialization.rb +54 -44
- data/lib/ldclient-rb/impl/repeating_task.rb +47 -0
- data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
- data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
- data/lib/ldclient-rb/impl/util.rb +62 -1
- data/lib/ldclient-rb/in_memory_store.rb +2 -2
- data/lib/ldclient-rb/integrations/consul.rb +9 -2
- data/lib/ldclient-rb/integrations/dynamodb.rb +47 -2
- data/lib/ldclient-rb/integrations/file_data.rb +108 -0
- data/lib/ldclient-rb/integrations/redis.rb +43 -3
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +594 -0
- data/lib/ldclient-rb/integrations/test_data.rb +213 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +14 -9
- data/lib/ldclient-rb/integrations.rb +2 -51
- data/lib/ldclient-rb/interfaces.rb +151 -1
- data/lib/ldclient-rb/ldclient.rb +175 -133
- data/lib/ldclient-rb/memoized_value.rb +1 -1
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
- data/lib/ldclient-rb/polling.rb +22 -41
- data/lib/ldclient-rb/reference.rb +274 -0
- data/lib/ldclient-rb/requestor.rb +7 -7
- data/lib/ldclient-rb/stream.rb +9 -9
- data/lib/ldclient-rb/util.rb +11 -17
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +2 -4
- metadata +49 -23
- data/lib/ldclient-rb/event_summarizer.rb +0 -55
- data/lib/ldclient-rb/file_data_source.rb +0 -314
- data/lib/ldclient-rb/impl/event_factory.rb +0 -126
- data/lib/ldclient-rb/newrelic.rb +0 -17
- data/lib/ldclient-rb/redis_store.rb +0 -88
- data/lib/ldclient-rb/user_filter.rb +0 -52
data/lib/ldclient-rb/ldclient.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
+
require "ldclient-rb/impl/big_segments"
|
1
2
|
require "ldclient-rb/impl/diagnostic_events"
|
2
3
|
require "ldclient-rb/impl/evaluator"
|
3
|
-
require "ldclient-rb/impl/event_factory"
|
4
4
|
require "ldclient-rb/impl/store_client_wrapper"
|
5
5
|
require "concurrent/atomics"
|
6
6
|
require "digest/sha1"
|
@@ -45,9 +45,6 @@ module LaunchDarkly
|
|
45
45
|
|
46
46
|
@sdk_key = sdk_key
|
47
47
|
|
48
|
-
@event_factory_default = EventFactory.new(false)
|
49
|
-
@event_factory_with_reasons = EventFactory.new(true)
|
50
|
-
|
51
48
|
# We need to wrap the feature store object with a FeatureStoreClientWrapper in order to add
|
52
49
|
# some necessary logic around updates. Unfortunately, we have code elsewhere that accesses
|
53
50
|
# the feature store through the Config object, so we need to make a new Config that uses
|
@@ -57,9 +54,13 @@ module LaunchDarkly
|
|
57
54
|
updated_config.instance_variable_set(:@feature_store, @store)
|
58
55
|
@config = updated_config
|
59
56
|
|
57
|
+
@big_segment_store_manager = Impl::BigSegmentStoreManager.new(config.big_segments, @config.logger)
|
58
|
+
@big_segment_store_status_provider = @big_segment_store_manager.status_provider
|
59
|
+
|
60
60
|
get_flag = lambda { |key| @store.get(FEATURES, key) }
|
61
61
|
get_segment = lambda { |key| @store.get(SEGMENTS, key) }
|
62
|
-
|
62
|
+
get_big_segments_membership = lambda { |key| @big_segment_store_manager.get_context_membership(key) }
|
63
|
+
@evaluator = LaunchDarkly::Impl::Evaluator.new(get_flag, get_segment, get_big_segments_membership, @config.logger)
|
63
64
|
|
64
65
|
if !@config.offline? && @config.send_events && !@config.diagnostic_opt_out?
|
65
66
|
diagnostic_accumulator = Impl::DiagnosticAccumulator.new(Impl::DiagnosticAccumulator.create_diagnostic_id(sdk_key))
|
@@ -119,26 +120,20 @@ module LaunchDarkly
|
|
119
120
|
end
|
120
121
|
|
121
122
|
#
|
122
|
-
#
|
123
|
-
# @param user [Hash] the user properties
|
124
|
-
# @param default [Boolean] (false) the value to use if the flag cannot be evaluated
|
125
|
-
# @return [Boolean] the flag value
|
126
|
-
# @deprecated Use {#variation} instead.
|
127
|
-
#
|
128
|
-
def toggle?(key, user, default = false)
|
129
|
-
@config.logger.warn { "[LDClient] toggle? is deprecated. Use variation instead" }
|
130
|
-
variation(key, user, default)
|
131
|
-
end
|
132
|
-
|
133
|
-
#
|
134
|
-
# Creates a hash string that can be used by the JavaScript SDK to identify a user.
|
123
|
+
# Creates a hash string that can be used by the JavaScript SDK to identify a context.
|
135
124
|
# For more information, see [Secure mode](https://docs.launchdarkly.com/sdk/features/secure-mode#ruby).
|
136
125
|
#
|
137
|
-
# @param
|
138
|
-
# @return [String] a hash string
|
126
|
+
# @param context [Hash, LDContext]
|
127
|
+
# @return [String, nil] a hash string or nil if the provided context was invalid
|
139
128
|
#
|
140
|
-
def secure_mode_hash(
|
141
|
-
|
129
|
+
def secure_mode_hash(context)
|
130
|
+
context = Impl::Context::make_context(context)
|
131
|
+
unless context.valid?
|
132
|
+
@config.logger.warn("secure_mode_hash called with invalid context: #{context.error}")
|
133
|
+
return nil
|
134
|
+
end
|
135
|
+
|
136
|
+
OpenSSL::HMAC.hexdigest("sha256", @sdk_key, context.fully_qualified_key)
|
142
137
|
end
|
143
138
|
|
144
139
|
#
|
@@ -164,44 +159,22 @@ module LaunchDarkly
|
|
164
159
|
end
|
165
160
|
|
166
161
|
#
|
167
|
-
# Determines the variation of a feature flag to present
|
168
|
-
#
|
169
|
-
# At a minimum, the user hash should contain a `:key`, which should be the unique
|
170
|
-
# identifier for your user (or, for an anonymous user, a session identifier or
|
171
|
-
# cookie).
|
172
|
-
#
|
173
|
-
# Other supported user attributes include IP address, country code, and an arbitrary hash of
|
174
|
-
# custom attributes. For more about the supported user properties and how they work in
|
175
|
-
# LaunchDarkly, see [Targeting users](https://docs.launchdarkly.com/home/flags/targeting-users).
|
176
|
-
#
|
177
|
-
# The optional `:privateAttributeNames` user property allows you to specify a list of
|
178
|
-
# attribute names that should not be sent back to LaunchDarkly.
|
179
|
-
# [Private attributes](https://docs.launchdarkly.com/home/users/attributes#creating-private-user-attributes)
|
180
|
-
# can also be configured globally in {Config}.
|
181
|
-
#
|
182
|
-
# @example Basic user hash
|
183
|
-
# {key: "my-user-id"}
|
184
|
-
#
|
185
|
-
# @example More complete user hash
|
186
|
-
# {key: "my-user-id", ip: "127.0.0.1", country: "US", custom: {customer_rank: 1000}}
|
187
|
-
#
|
188
|
-
# @example User with a private attribute
|
189
|
-
# {key: "my-user-id", email: "email@example.com", privateAttributeNames: ["email"]}
|
162
|
+
# Determines the variation of a feature flag to present for a context.
|
190
163
|
#
|
191
164
|
# @param key [String] the unique feature key for the feature flag, as shown
|
192
165
|
# on the LaunchDarkly dashboard
|
193
|
-
# @param
|
166
|
+
# @param context [Hash, LDContext] a hash or LDContext instance describing the context requesting the flag
|
194
167
|
# @param default the default value of the flag; this is used if there is an error
|
195
168
|
# condition making it impossible to find or evaluate the flag
|
196
169
|
#
|
197
|
-
# @return the variation
|
170
|
+
# @return the variation for the provided context, or the default value if there's an an error
|
198
171
|
#
|
199
|
-
def variation(key,
|
200
|
-
evaluate_internal(key,
|
172
|
+
def variation(key, context, default)
|
173
|
+
evaluate_internal(key, context, default, false).value
|
201
174
|
end
|
202
175
|
|
203
176
|
#
|
204
|
-
# Determines the variation of a feature flag for a
|
177
|
+
# Determines the variation of a feature flag for a context, like {#variation}, but also
|
205
178
|
# provides additional information about how this value was calculated.
|
206
179
|
#
|
207
180
|
# The return value of `variation_detail` is an {EvaluationDetail} object, which has
|
@@ -217,43 +190,48 @@ module LaunchDarkly
|
|
217
190
|
#
|
218
191
|
# @param key [String] the unique feature key for the feature flag, as shown
|
219
192
|
# on the LaunchDarkly dashboard
|
220
|
-
# @param
|
193
|
+
# @param context [Hash, LDContext] a hash or object describing the context requesting the flag,
|
221
194
|
# @param default the default value of the flag; this is used if there is an error
|
222
195
|
# condition making it impossible to find or evaluate the flag
|
223
196
|
#
|
224
197
|
# @return [EvaluationDetail] an object describing the result
|
225
198
|
#
|
226
|
-
def variation_detail(key,
|
227
|
-
evaluate_internal(key,
|
199
|
+
def variation_detail(key, context, default)
|
200
|
+
evaluate_internal(key, context, default, true)
|
228
201
|
end
|
229
202
|
|
230
203
|
#
|
231
|
-
# Registers the
|
232
|
-
# properties, so that LaunchDarkly will know about that
|
204
|
+
# Registers the context. This method simply creates an analytics event containing the context
|
205
|
+
# properties, so that LaunchDarkly will know about that context if it does not already.
|
233
206
|
#
|
234
|
-
# Calling {#variation} or {#variation_detail} also sends the
|
207
|
+
# Calling {#variation} or {#variation_detail} also sends the context information to
|
235
208
|
# LaunchDarkly (if events are enabled), so you only need to use {#identify} if you
|
236
|
-
# want to identify the
|
209
|
+
# want to identify the context without evaluating a flag.
|
237
210
|
#
|
238
211
|
# Note that event delivery is asynchronous, so the event may not actually be sent
|
239
212
|
# until later; see {#flush}.
|
240
213
|
#
|
241
|
-
# @param
|
242
|
-
# described in {#variation}
|
214
|
+
# @param context [Hash, LDContext] a hash or object describing the context to register
|
243
215
|
# @return [void]
|
244
216
|
#
|
245
|
-
def identify(
|
246
|
-
|
247
|
-
|
217
|
+
def identify(context)
|
218
|
+
context = LaunchDarkly::Impl::Context.make_context(context)
|
219
|
+
unless context.valid?
|
220
|
+
@config.logger.warn("Identify called with invalid context: #{context.error}")
|
221
|
+
return
|
222
|
+
end
|
223
|
+
|
224
|
+
if context.key == ""
|
225
|
+
@config.logger.warn("Identify called with empty key")
|
248
226
|
return
|
249
227
|
end
|
250
|
-
|
251
|
-
@event_processor.
|
228
|
+
|
229
|
+
@event_processor.record_identify_event(context)
|
252
230
|
end
|
253
231
|
|
254
232
|
#
|
255
|
-
# Tracks that a
|
256
|
-
# containing the specified event name (key),
|
233
|
+
# Tracks that a context performed an event. This method creates a "custom" analytics event
|
234
|
+
# containing the specified event name (key), context properties, and optional data.
|
257
235
|
#
|
258
236
|
# Note that event delivery is asynchronous, so the event may not actually be sent
|
259
237
|
# until later; see {#flush}.
|
@@ -264,8 +242,7 @@ module LaunchDarkly
|
|
264
242
|
# for the latest status.
|
265
243
|
#
|
266
244
|
# @param event_name [String] The name of the event
|
267
|
-
# @param
|
268
|
-
# described in {#variation}
|
245
|
+
# @param context [Hash, LDContext] a hash or object describing the context to track
|
269
246
|
# @param data [Hash] An optional hash containing any additional data associated with the event
|
270
247
|
# @param metric_value [Number] A numeric value used by the LaunchDarkly experimentation
|
271
248
|
# feature in numeric custom metrics. Can be omitted if this event is used by only
|
@@ -273,52 +250,22 @@ module LaunchDarkly
|
|
273
250
|
# for Data Export.
|
274
251
|
# @return [void]
|
275
252
|
#
|
276
|
-
def track(event_name,
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
end
|
281
|
-
sanitize_user(user)
|
282
|
-
@event_processor.add_event(@event_factory_default.new_custom_event(event_name, user, data, metric_value))
|
283
|
-
end
|
284
|
-
|
285
|
-
#
|
286
|
-
# Associates a new and old user object for analytics purposes via an alias event.
|
287
|
-
#
|
288
|
-
# @param current_context [Hash] The current version of a user.
|
289
|
-
# @param previous_context [Hash] The previous version of a user.
|
290
|
-
# @return [void]
|
291
|
-
#
|
292
|
-
def alias(current_context, previous_context)
|
293
|
-
if !current_context || current_context[:key].nil? || !previous_context || previous_context[:key].nil?
|
294
|
-
@config.logger.warn("Alias called with nil user or nil user key!")
|
253
|
+
def track(event_name, context, data = nil, metric_value = nil)
|
254
|
+
context = LaunchDarkly::Impl::Context.make_context(context)
|
255
|
+
unless context.valid?
|
256
|
+
@config.logger.warn("Track called with invalid context: #{context.error}")
|
295
257
|
return
|
296
258
|
end
|
297
|
-
sanitize_user(current_context)
|
298
|
-
sanitize_user(previous_context)
|
299
|
-
@event_processor.add_event(@event_factory_default.new_alias_event(current_context, previous_context))
|
300
|
-
end
|
301
259
|
|
302
|
-
|
303
|
-
# Returns all feature flag values for the given user.
|
304
|
-
#
|
305
|
-
# @deprecated Please use {#all_flags_state} instead. Current versions of the
|
306
|
-
# client-side SDK will not generate analytics events correctly if you pass the
|
307
|
-
# result of `all_flags`.
|
308
|
-
#
|
309
|
-
# @param user [Hash] The end user requesting the feature flags
|
310
|
-
# @return [Hash] a hash of feature flag keys to values
|
311
|
-
#
|
312
|
-
def all_flags(user)
|
313
|
-
all_flags_state(user).values_map
|
260
|
+
@event_processor.record_custom_event(context, event_name, data, metric_value)
|
314
261
|
end
|
315
262
|
|
316
263
|
#
|
317
|
-
# Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given
|
264
|
+
# Returns a {FeatureFlagsState} object that encapsulates the state of all feature flags for a given context,
|
318
265
|
# including the flag values and also metadata that can be used on the front end. This method does not
|
319
266
|
# send analytics events back to LaunchDarkly.
|
320
267
|
#
|
321
|
-
# @param
|
268
|
+
# @param context [Hash, LDContext] a hash or object describing the context requesting the flags,
|
322
269
|
# @param options [Hash] Optional parameters to control how the state is generated
|
323
270
|
# @option options [Boolean] :client_side_only (false) True if only flags marked for use with the
|
324
271
|
# client-side SDK should be included in the state. By default, all flags are included.
|
@@ -330,11 +277,21 @@ module LaunchDarkly
|
|
330
277
|
# of the JSON data if you are passing the flag state to the front end.
|
331
278
|
# @return [FeatureFlagsState] a {FeatureFlagsState} object which can be serialized to JSON
|
332
279
|
#
|
333
|
-
def all_flags_state(
|
280
|
+
def all_flags_state(context, options={})
|
334
281
|
return FeatureFlagsState.new(false) if @config.offline?
|
335
282
|
|
336
|
-
unless
|
337
|
-
@
|
283
|
+
unless initialized?
|
284
|
+
if @store.initialized?
|
285
|
+
@config.logger.warn { "Called all_flags_state before client initialization; using last known values from data store" }
|
286
|
+
else
|
287
|
+
@config.logger.warn { "Called all_flags_state before client initialization. Data store not available; returning empty state" }
|
288
|
+
return FeatureFlagsState.new(false)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
context = Impl::Context::make_context(context)
|
293
|
+
unless context.valid?
|
294
|
+
@config.logger.error { "[LDClient] Context was invalid for all_flags_state (#{context.error})" }
|
338
295
|
return FeatureFlagsState.new(false)
|
339
296
|
end
|
340
297
|
|
@@ -354,14 +311,25 @@ module LaunchDarkly
|
|
354
311
|
next
|
355
312
|
end
|
356
313
|
begin
|
357
|
-
|
358
|
-
state.add_flag(f, result.detail.value, result.detail.variation_index, with_reasons ? result.detail.reason : nil,
|
359
|
-
details_only_if_tracked)
|
314
|
+
detail = @evaluator.evaluate(f, context).detail
|
360
315
|
rescue => exn
|
316
|
+
detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION))
|
361
317
|
Util.log_exception(@config.logger, "Error evaluating flag \"#{k}\" in all_flags_state", exn)
|
362
|
-
state.add_flag(f, nil, nil, with_reasons ? EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION) : nil,
|
363
|
-
details_only_if_tracked)
|
364
318
|
end
|
319
|
+
|
320
|
+
requires_experiment_data = experiment?(f, detail.reason)
|
321
|
+
flag_state = {
|
322
|
+
key: f[:key],
|
323
|
+
value: detail.value,
|
324
|
+
variation: detail.variation_index,
|
325
|
+
reason: detail.reason,
|
326
|
+
version: f[:version],
|
327
|
+
trackEvents: f[:trackEvents] || requires_experiment_data,
|
328
|
+
trackReason: requires_experiment_data,
|
329
|
+
debugEventsUntilDate: f[:debugEventsUntilDate],
|
330
|
+
}
|
331
|
+
|
332
|
+
state.add_flag(flag_state, with_reasons, details_only_if_tracked)
|
365
333
|
end
|
366
334
|
|
367
335
|
state
|
@@ -375,9 +343,18 @@ module LaunchDarkly
|
|
375
343
|
@config.logger.info { "[LDClient] Closing LaunchDarkly client..." }
|
376
344
|
@data_source.stop
|
377
345
|
@event_processor.stop
|
346
|
+
@big_segment_store_manager.stop
|
378
347
|
@store.stop
|
379
348
|
end
|
380
349
|
|
350
|
+
#
|
351
|
+
# Returns an interface for tracking the status of a Big Segment store.
|
352
|
+
#
|
353
|
+
# The {BigSegmentStoreStatusProvider} has methods for checking whether the Big Segment store
|
354
|
+
# is (as far as the SDK knows) currently operational and tracking changes in this status.
|
355
|
+
#
|
356
|
+
attr_reader :big_segment_store_status_provider
|
357
|
+
|
381
358
|
private
|
382
359
|
|
383
360
|
def create_default_data_source(sdk_key, config, diagnostic_accumulator)
|
@@ -395,32 +372,34 @@ module LaunchDarkly
|
|
395
372
|
end
|
396
373
|
end
|
397
374
|
|
375
|
+
# @param context [Hash, LDContext]
|
398
376
|
# @return [EvaluationDetail]
|
399
|
-
def evaluate_internal(key,
|
377
|
+
def evaluate_internal(key, context, default, with_reasons)
|
400
378
|
if @config.offline?
|
401
379
|
return Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
402
380
|
end
|
403
381
|
|
404
|
-
|
405
|
-
@config.logger.error { "[LDClient] Must specify
|
382
|
+
if context.nil?
|
383
|
+
@config.logger.error { "[LDClient] Must specify context" }
|
406
384
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
407
385
|
return detail
|
408
386
|
end
|
409
387
|
|
410
|
-
|
411
|
-
|
388
|
+
context = Impl::Context::make_context(context)
|
389
|
+
unless context.valid?
|
390
|
+
@config.logger.error { "[LDClient] Context was invalid for flag evaluation (#{context.error}); returning default value" }
|
412
391
|
detail = Evaluator.error_result(EvaluationReason::ERROR_USER_NOT_SPECIFIED, default)
|
413
392
|
return detail
|
414
393
|
end
|
415
394
|
|
416
|
-
|
395
|
+
unless initialized?
|
417
396
|
if @store.initialized?
|
418
397
|
@config.logger.warn { "[LDClient] Client has not finished initializing; using last known values from feature store" }
|
419
398
|
else
|
420
399
|
@config.logger.error { "[LDClient] Client has not finished initializing; feature store unavailable, returning default value" }
|
421
400
|
detail = Evaluator.error_result(EvaluationReason::ERROR_CLIENT_NOT_READY, default)
|
422
|
-
|
423
|
-
return
|
401
|
+
record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
|
402
|
+
return detail
|
424
403
|
end
|
425
404
|
end
|
426
405
|
|
@@ -429,35 +408,98 @@ module LaunchDarkly
|
|
429
408
|
if feature.nil?
|
430
409
|
@config.logger.info { "[LDClient] Unknown feature flag \"#{key}\". Returning default value" }
|
431
410
|
detail = Evaluator.error_result(EvaluationReason::ERROR_FLAG_NOT_FOUND, default)
|
432
|
-
|
411
|
+
record_unknown_flag_eval(key, context, default, detail.reason, with_reasons)
|
433
412
|
return detail
|
434
413
|
end
|
435
414
|
|
436
415
|
begin
|
437
|
-
res = @evaluator.evaluate(feature,
|
438
|
-
|
439
|
-
res.
|
440
|
-
|
416
|
+
res = @evaluator.evaluate(feature, context)
|
417
|
+
unless res.prereq_evals.nil?
|
418
|
+
res.prereq_evals.each do |prereq_eval|
|
419
|
+
record_prereq_flag_eval(prereq_eval.prereq_flag, prereq_eval.prereq_of_flag, context, prereq_eval.detail, with_reasons)
|
441
420
|
end
|
442
421
|
end
|
443
422
|
detail = res.detail
|
444
423
|
if detail.default_value?
|
445
424
|
detail = EvaluationDetail.new(default, nil, detail.reason)
|
446
425
|
end
|
447
|
-
|
448
|
-
|
426
|
+
record_flag_eval(feature, context, detail, default, with_reasons)
|
427
|
+
detail
|
449
428
|
rescue => exn
|
450
429
|
Util.log_exception(@config.logger, "Error evaluating feature flag \"#{key}\"", exn)
|
451
430
|
detail = Evaluator.error_result(EvaluationReason::ERROR_EXCEPTION, default)
|
452
|
-
|
453
|
-
|
431
|
+
record_flag_eval_error(feature, context, default, detail.reason, with_reasons)
|
432
|
+
detail
|
454
433
|
end
|
455
434
|
end
|
456
435
|
|
457
|
-
def
|
458
|
-
|
459
|
-
|
436
|
+
private def record_flag_eval(flag, context, detail, default, with_reasons)
|
437
|
+
add_experiment_data = experiment?(flag, detail.reason)
|
438
|
+
@event_processor.record_eval_event(
|
439
|
+
context,
|
440
|
+
flag[:key],
|
441
|
+
flag[:version],
|
442
|
+
detail.variation_index,
|
443
|
+
detail.value,
|
444
|
+
(add_experiment_data || with_reasons) ? detail.reason : nil,
|
445
|
+
default,
|
446
|
+
add_experiment_data || flag[:trackEvents] || false,
|
447
|
+
flag[:debugEventsUntilDate],
|
448
|
+
nil
|
449
|
+
)
|
450
|
+
end
|
451
|
+
|
452
|
+
private def record_prereq_flag_eval(prereq_flag, prereq_of_flag, context, detail, with_reasons)
|
453
|
+
add_experiment_data = experiment?(prereq_flag, detail.reason)
|
454
|
+
@event_processor.record_eval_event(
|
455
|
+
context,
|
456
|
+
prereq_flag[:key],
|
457
|
+
prereq_flag[:version],
|
458
|
+
detail.variation_index,
|
459
|
+
detail.value,
|
460
|
+
(add_experiment_data || with_reasons) ? detail.reason : nil,
|
461
|
+
nil,
|
462
|
+
add_experiment_data || prereq_flag[:trackEvents] || false,
|
463
|
+
prereq_flag[:debugEventsUntilDate],
|
464
|
+
prereq_of_flag[:key]
|
465
|
+
)
|
466
|
+
end
|
467
|
+
|
468
|
+
private def record_flag_eval_error(flag, context, default, reason, with_reasons)
|
469
|
+
@event_processor.record_eval_event(context, flag[:key], flag[:version], nil, default, with_reasons ? reason : nil, default,
|
470
|
+
flag[:trackEvents], flag[:debugEventsUntilDate], nil)
|
471
|
+
end
|
472
|
+
|
473
|
+
#
|
474
|
+
# @param flag_key [String]
|
475
|
+
# @param context [LaunchDarkly::LDContext]
|
476
|
+
# @param default [any]
|
477
|
+
# @param reason [LaunchDarkly::EvaluationReason]
|
478
|
+
# @param with_reasons [Boolean]
|
479
|
+
#
|
480
|
+
private def record_unknown_flag_eval(flag_key, context, default, reason, with_reasons)
|
481
|
+
@event_processor.record_eval_event(context, flag_key, nil, nil, default, with_reasons ? reason : nil, default,
|
482
|
+
false, nil, nil)
|
483
|
+
end
|
484
|
+
|
485
|
+
private def experiment?(flag, reason)
|
486
|
+
return false unless reason
|
487
|
+
|
488
|
+
if reason.in_experiment
|
489
|
+
return true
|
490
|
+
end
|
491
|
+
|
492
|
+
case reason[:kind]
|
493
|
+
when 'RULE_MATCH'
|
494
|
+
index = reason[:ruleIndex]
|
495
|
+
unless index.nil?
|
496
|
+
rules = flag[:rules] || []
|
497
|
+
return index >= 0 && index < rules.length && rules[index][:trackEvents]
|
498
|
+
end
|
499
|
+
when 'FALLTHROUGH'
|
500
|
+
return !!flag[:trackEventsFallthrough]
|
460
501
|
end
|
502
|
+
false
|
461
503
|
end
|
462
504
|
end
|
463
505
|
|
@@ -17,7 +17,7 @@ module LaunchDarkly
|
|
17
17
|
# Attempts to submit a job, but only if a worker is available. Unlike the regular post method,
|
18
18
|
# this returns a value: true if the job was submitted, false if all workers are busy.
|
19
19
|
def post
|
20
|
-
|
20
|
+
unless @semaphore.try_acquire(1)
|
21
21
|
return
|
22
22
|
end
|
23
23
|
@pool.post do
|
data/lib/ldclient-rb/polling.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require "ldclient-rb/impl/repeating_task"
|
2
|
+
|
1
3
|
require "concurrent/atomics"
|
2
4
|
require "thread"
|
3
5
|
|
@@ -9,8 +11,8 @@ module LaunchDarkly
|
|
9
11
|
@requestor = requestor
|
10
12
|
@initialized = Concurrent::AtomicBoolean.new(false)
|
11
13
|
@started = Concurrent::AtomicBoolean.new(false)
|
12
|
-
@stopped = Concurrent::AtomicBoolean.new(false)
|
13
14
|
@ready = Concurrent::Event.new
|
15
|
+
@task = Impl::RepeatingTask.new(@config.poll_interval, 0, -> { self.poll }, @config.logger)
|
14
16
|
end
|
15
17
|
|
16
18
|
def initialized?
|
@@ -20,56 +22,35 @@ module LaunchDarkly
|
|
20
22
|
def start
|
21
23
|
return @ready unless @started.make_true
|
22
24
|
@config.logger.info { "[LDClient] Initializing polling connection" }
|
23
|
-
|
25
|
+
@task.start
|
24
26
|
@ready
|
25
27
|
end
|
26
28
|
|
27
29
|
def stop
|
28
|
-
|
29
|
-
|
30
|
-
@worker.run # causes the thread to wake up if it's currently in a sleep
|
31
|
-
@worker.join
|
32
|
-
end
|
33
|
-
@config.logger.info { "[LDClient] Polling connection stopped" }
|
34
|
-
end
|
30
|
+
@task.stop
|
31
|
+
@config.logger.info { "[LDClient] Polling connection stopped" }
|
35
32
|
end
|
36
33
|
|
37
34
|
def poll
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
@
|
43
|
-
|
44
|
-
|
45
|
-
end
|
46
|
-
end
|
47
|
-
|
48
|
-
def create_worker
|
49
|
-
@worker = Thread.new do
|
50
|
-
@config.logger.debug { "[LDClient] Starting polling worker" }
|
51
|
-
while !@stopped.value do
|
52
|
-
started_at = Time.now
|
53
|
-
begin
|
54
|
-
poll
|
55
|
-
rescue UnexpectedResponseError => e
|
56
|
-
message = Util.http_error_message(e.status, "polling request", "will retry")
|
57
|
-
@config.logger.error { "[LDClient] #{message}" };
|
58
|
-
if !Util.http_error_recoverable?(e.status)
|
59
|
-
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
|
60
|
-
stop
|
61
|
-
end
|
62
|
-
rescue StandardError => exn
|
63
|
-
Util.log_exception(@config.logger, "Exception while polling", exn)
|
64
|
-
end
|
65
|
-
delta = @config.poll_interval - (Time.now - started_at)
|
66
|
-
if delta > 0
|
67
|
-
sleep(delta)
|
35
|
+
begin
|
36
|
+
all_data = @requestor.request_all_data
|
37
|
+
if all_data
|
38
|
+
@config.feature_store.init(all_data)
|
39
|
+
if @initialized.make_true
|
40
|
+
@config.logger.info { "[LDClient] Polling connection initialized" }
|
41
|
+
@ready.set
|
68
42
|
end
|
69
43
|
end
|
44
|
+
rescue UnexpectedResponseError => e
|
45
|
+
message = Util.http_error_message(e.status, "polling request", "will retry")
|
46
|
+
@config.logger.error { "[LDClient] #{message}" }
|
47
|
+
unless Util.http_error_recoverable?(e.status)
|
48
|
+
@ready.set # if client was waiting on us, make it stop waiting - has no effect if already set
|
49
|
+
stop
|
50
|
+
end
|
51
|
+
rescue StandardError => e
|
52
|
+
Util.log_exception(@config.logger, "Exception while polling", e)
|
70
53
|
end
|
71
54
|
end
|
72
|
-
|
73
|
-
private :poll, :create_worker
|
74
55
|
end
|
75
56
|
end
|