launchdarkly-server-sdk 6.2.5 → 7.0.0
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 +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
|