launchdarkly-server-sdk 7.0.2 → 8.4.2
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 +9 -4
- data/lib/ldclient-rb/config.rb +50 -70
- data/lib/ldclient-rb/context.rb +65 -50
- data/lib/ldclient-rb/evaluation_detail.rb +5 -1
- data/lib/ldclient-rb/events.rb +81 -8
- data/lib/ldclient-rb/impl/big_segments.rb +1 -1
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +3 -3
- data/lib/ldclient-rb/impl/context_filter.rb +30 -9
- data/lib/ldclient-rb/impl/data_source.rb +188 -0
- data/lib/ldclient-rb/impl/data_store.rb +59 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
- data/lib/ldclient-rb/impl/evaluation_with_hook_result.rb +34 -0
- data/lib/ldclient-rb/impl/event_sender.rb +1 -0
- data/lib/ldclient-rb/impl/event_types.rb +61 -3
- data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
- data/lib/ldclient-rb/impl/integrations/consul_impl.rb +12 -0
- data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +8 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +16 -3
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +19 -2
- data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
- data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
- data/lib/ldclient-rb/impl/model/feature_flag.rb +25 -3
- data/lib/ldclient-rb/impl/repeating_task.rb +2 -3
- data/lib/ldclient-rb/impl/sampler.rb +25 -0
- data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
- data/lib/ldclient-rb/in_memory_store.rb +7 -0
- data/lib/ldclient-rb/integrations/file_data.rb +1 -1
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +84 -15
- data/lib/ldclient-rb/integrations/test_data.rb +3 -3
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +11 -0
- data/lib/ldclient-rb/interfaces.rb +671 -0
- data/lib/ldclient-rb/ldclient.rb +313 -22
- data/lib/ldclient-rb/migrations.rb +230 -0
- data/lib/ldclient-rb/polling.rb +51 -5
- data/lib/ldclient-rb/reference.rb +11 -0
- data/lib/ldclient-rb/requestor.rb +5 -5
- data/lib/ldclient-rb/stream.rb +91 -29
- data/lib/ldclient-rb/util.rb +89 -0
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +1 -0
- metadata +44 -6
@@ -0,0 +1,25 @@
|
|
1
|
+
module LaunchDarkly
|
2
|
+
module Impl
|
3
|
+
class Sampler
|
4
|
+
#
|
5
|
+
# @param random [Random]
|
6
|
+
#
|
7
|
+
def initialize(random)
|
8
|
+
@random = random
|
9
|
+
end
|
10
|
+
|
11
|
+
#
|
12
|
+
# @param ratio [Int]
|
13
|
+
#
|
14
|
+
# @return [Boolean]
|
15
|
+
#
|
16
|
+
def sample(ratio)
|
17
|
+
return false unless ratio.is_a? Integer
|
18
|
+
return false if ratio <= 0
|
19
|
+
return true if ratio == 1
|
20
|
+
|
21
|
+
@random.rand(1.0) < 1.0 / ratio
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require "concurrent"
|
1
2
|
require "ldclient-rb/interfaces"
|
2
3
|
require "ldclient-rb/impl/store_data_set_sorter"
|
3
4
|
|
@@ -5,34 +6,45 @@ module LaunchDarkly
|
|
5
6
|
module Impl
|
6
7
|
#
|
7
8
|
# Provides additional behavior that the client requires before or after feature store operations.
|
8
|
-
#
|
9
|
-
# to provide an update listener capability.
|
9
|
+
# This just means sorting the data set for init() and dealing with data store status listeners.
|
10
10
|
#
|
11
11
|
class FeatureStoreClientWrapper
|
12
12
|
include Interfaces::FeatureStore
|
13
13
|
|
14
|
-
def initialize(store)
|
14
|
+
def initialize(store, store_update_sink, logger)
|
15
|
+
# @type [LaunchDarkly::Interfaces::FeatureStore]
|
15
16
|
@store = store
|
17
|
+
|
18
|
+
@monitoring_enabled = does_store_support_monitoring?
|
19
|
+
|
20
|
+
# @type [LaunchDarkly::Impl::DataStore::UpdateSink]
|
21
|
+
@store_update_sink = store_update_sink
|
22
|
+
@logger = logger
|
23
|
+
|
24
|
+
@mutex = Mutex.new # Covers the following variables
|
25
|
+
@last_available = true
|
26
|
+
# @type [LaunchDarkly::Impl::RepeatingTask, nil]
|
27
|
+
@poller = nil
|
16
28
|
end
|
17
29
|
|
18
30
|
def init(all_data)
|
19
|
-
@store.init(FeatureStoreDataSetSorter.sort_all_collections(all_data))
|
31
|
+
wrapper { @store.init(FeatureStoreDataSetSorter.sort_all_collections(all_data)) }
|
20
32
|
end
|
21
33
|
|
22
34
|
def get(kind, key)
|
23
|
-
@store.get(kind, key)
|
35
|
+
wrapper { @store.get(kind, key) }
|
24
36
|
end
|
25
37
|
|
26
38
|
def all(kind)
|
27
|
-
@store.all(kind)
|
39
|
+
wrapper { @store.all(kind) }
|
28
40
|
end
|
29
41
|
|
30
42
|
def upsert(kind, item)
|
31
|
-
@store.upsert(kind, item)
|
43
|
+
wrapper { @store.upsert(kind, item) }
|
32
44
|
end
|
33
45
|
|
34
46
|
def delete(kind, key, version)
|
35
|
-
@store.delete(kind, key, version)
|
47
|
+
wrapper { @store.delete(kind, key, version) }
|
36
48
|
end
|
37
49
|
|
38
50
|
def initialized?
|
@@ -41,6 +53,88 @@ module LaunchDarkly
|
|
41
53
|
|
42
54
|
def stop
|
43
55
|
@store.stop
|
56
|
+
@mutex.synchronize do
|
57
|
+
return if @poller.nil?
|
58
|
+
|
59
|
+
@poller.stop
|
60
|
+
@poller = nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def monitoring_enabled?
|
65
|
+
@monitoring_enabled
|
66
|
+
end
|
67
|
+
|
68
|
+
private def wrapper()
|
69
|
+
begin
|
70
|
+
yield
|
71
|
+
rescue => e
|
72
|
+
update_availability(false) if @monitoring_enabled
|
73
|
+
raise
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private def update_availability(available)
|
78
|
+
@mutex.synchronize do
|
79
|
+
return if available == @last_available
|
80
|
+
@last_available = available
|
81
|
+
end
|
82
|
+
|
83
|
+
status = LaunchDarkly::Interfaces::DataStore::Status.new(available, false)
|
84
|
+
|
85
|
+
@logger.warn("Persistent store is available again") if available
|
86
|
+
|
87
|
+
@store_update_sink.update_status(status)
|
88
|
+
|
89
|
+
if available
|
90
|
+
@mutex.synchronize do
|
91
|
+
return if @poller.nil?
|
92
|
+
|
93
|
+
@poller.stop
|
94
|
+
@poller = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
@logger.warn("Detected persistent store unavailability; updates will be cached until it recovers.")
|
101
|
+
|
102
|
+
task = Impl::RepeatingTask.new(0.5, 0, -> { self.check_availability }, @logger)
|
103
|
+
|
104
|
+
@mutex.synchronize do
|
105
|
+
@poller = task
|
106
|
+
@poller.start
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
private def check_availability
|
111
|
+
begin
|
112
|
+
update_availability(true) if @store.available?
|
113
|
+
rescue => e
|
114
|
+
@logger.error("Unexpected error from data store status function: #{e}")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# This methods determines whether the wrapped store can support enabling monitoring.
|
119
|
+
#
|
120
|
+
# The wrapped store must provide a monitoring_enabled method, which must
|
121
|
+
# be true. But this alone is not sufficient.
|
122
|
+
#
|
123
|
+
# Because this class wraps all interactions with a provided store, it can
|
124
|
+
# technically "monitor" any store. However, monitoring also requires that
|
125
|
+
# we notify listeners when the store is available again.
|
126
|
+
#
|
127
|
+
# We determine this by checking the store's `available?` method, so this
|
128
|
+
# is also a requirement for monitoring support.
|
129
|
+
#
|
130
|
+
# These extra checks won't be necessary once `available` becomes a part
|
131
|
+
# of the core interface requirements and this class no longer wraps every
|
132
|
+
# feature store.
|
133
|
+
private def does_store_support_monitoring?
|
134
|
+
return false unless @store.respond_to? :monitoring_enabled?
|
135
|
+
return false unless @store.respond_to? :available?
|
136
|
+
|
137
|
+
@store.monitoring_enabled?
|
44
138
|
end
|
45
139
|
end
|
46
140
|
end
|
@@ -23,6 +23,9 @@ module LaunchDarkly
|
|
23
23
|
priority: 0,
|
24
24
|
}.freeze
|
25
25
|
|
26
|
+
# @private
|
27
|
+
ALL_KINDS = [FEATURES, SEGMENTS].freeze
|
28
|
+
|
26
29
|
#
|
27
30
|
# Default implementation of the LaunchDarkly client's feature store, using an in-memory
|
28
31
|
# cache. This object holds feature flags and related data received from LaunchDarkly.
|
@@ -37,6 +40,10 @@ module LaunchDarkly
|
|
37
40
|
@initialized = Concurrent::AtomicBoolean.new(false)
|
38
41
|
end
|
39
42
|
|
43
|
+
def monitoring_enabled?
|
44
|
+
false
|
45
|
+
end
|
46
|
+
|
40
47
|
def get(kind, key)
|
41
48
|
@lock.with_read_lock do
|
42
49
|
coll = @items[kind]
|
@@ -101,7 +101,7 @@ module LaunchDarkly
|
|
101
101
|
#
|
102
102
|
def self.data_source(options={})
|
103
103
|
lambda { |sdk_key, config|
|
104
|
-
Impl::Integrations::FileDataSourceImpl.new(config.feature_store, config.logger, options) }
|
104
|
+
Impl::Integrations::FileDataSourceImpl.new(config.feature_store, config.data_source_update_sink, config.logger, options) }
|
105
105
|
end
|
106
106
|
end
|
107
107
|
end
|
@@ -43,6 +43,47 @@ module LaunchDarkly
|
|
43
43
|
self
|
44
44
|
end
|
45
45
|
|
46
|
+
#
|
47
|
+
# Set the migration related settings for this feature flag.
|
48
|
+
#
|
49
|
+
# The settings hash should be built using the {FlagMigrationSettingsBuilder}.
|
50
|
+
#
|
51
|
+
# @param settings [Hash]
|
52
|
+
# @return [FlagBuilder] the builder
|
53
|
+
#
|
54
|
+
def migration_settings(settings)
|
55
|
+
@migration_settings = settings
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
#
|
60
|
+
# Set the sampling ratio for this flag. This ratio is used to control the emission rate of feature, debug, and
|
61
|
+
# migration op events.
|
62
|
+
#
|
63
|
+
# General usage should not require interacting with this method.
|
64
|
+
#
|
65
|
+
# @param ratio [Integer]
|
66
|
+
# @return [FlagBuilder]
|
67
|
+
#
|
68
|
+
def sampling_ratio(ratio)
|
69
|
+
@sampling_ratio = ratio
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
#
|
74
|
+
# Set the option to exclude this flag from summary events. This is used to control the size of the summary event
|
75
|
+
# in the event certain flag payloads are large.
|
76
|
+
#
|
77
|
+
# General usage should not require interacting with this method.
|
78
|
+
#
|
79
|
+
# @param exclude [Boolean]
|
80
|
+
# @return [FlagBuilder]
|
81
|
+
#
|
82
|
+
def exclude_from_summaries(exclude)
|
83
|
+
@exclude_from_summaries = exclude
|
84
|
+
self
|
85
|
+
end
|
86
|
+
|
46
87
|
#
|
47
88
|
# Specifies the fallthrough variation. The fallthrough is the value
|
48
89
|
# that is returned if targeting is on and the context was not matched by a more specific
|
@@ -128,11 +169,6 @@ module LaunchDarkly
|
|
128
169
|
end
|
129
170
|
end
|
130
171
|
|
131
|
-
#
|
132
|
-
# @deprecated Backwards compatibility alias for #variation_for_all
|
133
|
-
#
|
134
|
-
alias_method :variation_for_all_users, :variation_for_all
|
135
|
-
|
136
172
|
#
|
137
173
|
# Sets the flag to always return the specified variation value for all context.
|
138
174
|
#
|
@@ -148,11 +184,6 @@ module LaunchDarkly
|
|
148
184
|
variations(value).variation_for_all(0)
|
149
185
|
end
|
150
186
|
|
151
|
-
#
|
152
|
-
# @deprecated Backwards compatibility alias for #value_for_all
|
153
|
-
#
|
154
|
-
alias_method :value_for_all_users, :value_for_all
|
155
|
-
|
156
187
|
#
|
157
188
|
# Sets the flag to return the specified variation for a specific context key when targeting
|
158
189
|
# is on.
|
@@ -315,11 +346,6 @@ module LaunchDarkly
|
|
315
346
|
self
|
316
347
|
end
|
317
348
|
|
318
|
-
#
|
319
|
-
# @deprecated Backwards compatibility alias for #clear_targets
|
320
|
-
#
|
321
|
-
alias_method :clear_user_targets, :clear_targets
|
322
|
-
|
323
349
|
#
|
324
350
|
# Removes any existing rules from the flag.
|
325
351
|
# This undoes the effect of methods like {#if_match}
|
@@ -376,6 +402,18 @@ module LaunchDarkly
|
|
376
402
|
res[:fallthrough] = { variation: @fallthrough_variation }
|
377
403
|
end
|
378
404
|
|
405
|
+
unless @migration_settings.nil?
|
406
|
+
res[:migration] = @migration_settings
|
407
|
+
end
|
408
|
+
|
409
|
+
unless @sampling_ratio.nil? || @sampling_ratio == 1
|
410
|
+
res[:samplingRatio] = @sampling_ratio
|
411
|
+
end
|
412
|
+
|
413
|
+
unless @exclude_from_summaries.nil? || !@exclude_from_summaries
|
414
|
+
res[:excludeFromSummaries] = @exclude_from_summaries
|
415
|
+
end
|
416
|
+
|
379
417
|
unless @targets.nil?
|
380
418
|
targets = []
|
381
419
|
context_targets = []
|
@@ -403,6 +441,37 @@ module LaunchDarkly
|
|
403
441
|
res
|
404
442
|
end
|
405
443
|
|
444
|
+
#
|
445
|
+
# A builder for feature flag migration settings to be used with {FlagBuilder}.
|
446
|
+
#
|
447
|
+
# In the LaunchDarkly model, a flag can be a standard feature flag, or it can be a migration-related flag, in
|
448
|
+
# which case it has migration-specified related settings. These settings control things like the rate at which
|
449
|
+
# reads are tested for consistency between origins.
|
450
|
+
#
|
451
|
+
class FlagMigrationSettingsBuilder
|
452
|
+
def initialize()
|
453
|
+
@check_ratio = nil
|
454
|
+
end
|
455
|
+
|
456
|
+
#
|
457
|
+
# @param ratio [Integer]
|
458
|
+
# @return [FlagMigrationSettingsBuilder]
|
459
|
+
#
|
460
|
+
def check_ratio(ratio)
|
461
|
+
return unless ratio.is_a? Integer
|
462
|
+
@check_ratio = ratio
|
463
|
+
self
|
464
|
+
end
|
465
|
+
|
466
|
+
def build
|
467
|
+
return nil if @check_ratio.nil? || @check_ratio == 1
|
468
|
+
|
469
|
+
{
|
470
|
+
"checkRatio": @check_ratio,
|
471
|
+
}
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
406
475
|
#
|
407
476
|
# A builder for feature flag rules to be used with {FlagBuilder}.
|
408
477
|
#
|
@@ -90,7 +90,7 @@ module LaunchDarkly
|
|
90
90
|
#
|
91
91
|
def flag(key)
|
92
92
|
existing_builder = @lock.with_read_lock { @flag_builders[key] }
|
93
|
-
if existing_builder.nil?
|
93
|
+
if existing_builder.nil?
|
94
94
|
FlagBuilder.new(key).boolean_flag
|
95
95
|
else
|
96
96
|
existing_builder.clone
|
@@ -118,7 +118,7 @@ module LaunchDarkly
|
|
118
118
|
@flag_builders[flag_builder.key] = flag_builder
|
119
119
|
version = 0
|
120
120
|
flag_key = flag_builder.key.to_sym
|
121
|
-
if @current_flags[flag_key]
|
121
|
+
if @current_flags[flag_key]
|
122
122
|
version = @current_flags[flag_key][:version]
|
123
123
|
end
|
124
124
|
new_flag = Impl::Model.deserialize(FEATURES, flag_builder.build(version+1))
|
@@ -175,7 +175,7 @@ module LaunchDarkly
|
|
175
175
|
key = item.key.to_sym
|
176
176
|
@lock.with_write_lock do
|
177
177
|
old_item = current[key]
|
178
|
-
unless old_item.nil?
|
178
|
+
unless old_item.nil?
|
179
179
|
data = item.as_json
|
180
180
|
data[:version] = old_item.version + 1
|
181
181
|
item = Impl::Model.deserialize(kind, data)
|
@@ -43,6 +43,17 @@ module LaunchDarkly
|
|
43
43
|
end
|
44
44
|
|
45
45
|
@inited = Concurrent::AtomicBoolean.new(false)
|
46
|
+
@has_available_method = @core.respond_to? :available?
|
47
|
+
end
|
48
|
+
|
49
|
+
def monitoring_enabled?
|
50
|
+
@has_available_method
|
51
|
+
end
|
52
|
+
|
53
|
+
def available?
|
54
|
+
return false unless @has_available_method
|
55
|
+
|
56
|
+
@core.available?
|
46
57
|
end
|
47
58
|
|
48
59
|
def init(all_data)
|