launchdarkly-server-sdk 6.3.0 → 8.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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -4
  3. data/lib/ldclient-rb/config.rb +112 -62
  4. data/lib/ldclient-rb/context.rb +444 -0
  5. data/lib/ldclient-rb/evaluation_detail.rb +26 -22
  6. data/lib/ldclient-rb/events.rb +256 -146
  7. data/lib/ldclient-rb/flags_state.rb +26 -15
  8. data/lib/ldclient-rb/impl/big_segments.rb +18 -18
  9. data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
  10. data/lib/ldclient-rb/impl/context.rb +96 -0
  11. data/lib/ldclient-rb/impl/context_filter.rb +145 -0
  12. data/lib/ldclient-rb/impl/data_source.rb +188 -0
  13. data/lib/ldclient-rb/impl/data_store.rb +59 -0
  14. data/lib/ldclient-rb/impl/dependency_tracker.rb +102 -0
  15. data/lib/ldclient-rb/impl/diagnostic_events.rb +9 -10
  16. data/lib/ldclient-rb/impl/evaluator.rb +386 -142
  17. data/lib/ldclient-rb/impl/evaluator_bucketing.rb +40 -41
  18. data/lib/ldclient-rb/impl/evaluator_helpers.rb +50 -0
  19. data/lib/ldclient-rb/impl/evaluator_operators.rb +26 -55
  20. data/lib/ldclient-rb/impl/event_sender.rb +7 -6
  21. data/lib/ldclient-rb/impl/event_summarizer.rb +68 -0
  22. data/lib/ldclient-rb/impl/event_types.rb +136 -0
  23. data/lib/ldclient-rb/impl/flag_tracker.rb +58 -0
  24. data/lib/ldclient-rb/impl/integrations/consul_impl.rb +19 -7
  25. data/lib/ldclient-rb/impl/integrations/dynamodb_impl.rb +38 -30
  26. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +24 -11
  27. data/lib/ldclient-rb/impl/integrations/redis_impl.rb +109 -12
  28. data/lib/ldclient-rb/impl/migrations/migrator.rb +287 -0
  29. data/lib/ldclient-rb/impl/migrations/tracker.rb +136 -0
  30. data/lib/ldclient-rb/impl/model/clause.rb +45 -0
  31. data/lib/ldclient-rb/impl/model/feature_flag.rb +255 -0
  32. data/lib/ldclient-rb/impl/model/preprocessed_data.rb +64 -0
  33. data/lib/ldclient-rb/impl/model/segment.rb +132 -0
  34. data/lib/ldclient-rb/impl/model/serialization.rb +54 -44
  35. data/lib/ldclient-rb/impl/repeating_task.rb +3 -4
  36. data/lib/ldclient-rb/impl/sampler.rb +25 -0
  37. data/lib/ldclient-rb/impl/store_client_wrapper.rb +102 -8
  38. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +2 -2
  39. data/lib/ldclient-rb/impl/unbounded_pool.rb +1 -1
  40. data/lib/ldclient-rb/impl/util.rb +59 -1
  41. data/lib/ldclient-rb/in_memory_store.rb +9 -2
  42. data/lib/ldclient-rb/integrations/consul.rb +2 -2
  43. data/lib/ldclient-rb/integrations/dynamodb.rb +2 -2
  44. data/lib/ldclient-rb/integrations/file_data.rb +4 -4
  45. data/lib/ldclient-rb/integrations/redis.rb +5 -5
  46. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +287 -62
  47. data/lib/ldclient-rb/integrations/test_data.rb +18 -14
  48. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +20 -9
  49. data/lib/ldclient-rb/interfaces.rb +600 -14
  50. data/lib/ldclient-rb/ldclient.rb +314 -134
  51. data/lib/ldclient-rb/memoized_value.rb +1 -1
  52. data/lib/ldclient-rb/migrations.rb +230 -0
  53. data/lib/ldclient-rb/non_blocking_thread_pool.rb +1 -1
  54. data/lib/ldclient-rb/polling.rb +52 -6
  55. data/lib/ldclient-rb/reference.rb +274 -0
  56. data/lib/ldclient-rb/requestor.rb +9 -11
  57. data/lib/ldclient-rb/stream.rb +96 -34
  58. data/lib/ldclient-rb/util.rb +97 -14
  59. data/lib/ldclient-rb/version.rb +1 -1
  60. data/lib/ldclient-rb.rb +3 -4
  61. metadata +65 -23
  62. data/lib/ldclient-rb/event_summarizer.rb +0 -55
  63. data/lib/ldclient-rb/file_data_source.rb +0 -23
  64. data/lib/ldclient-rb/impl/event_factory.rb +0 -126
  65. data/lib/ldclient-rb/newrelic.rb +0 -17
  66. data/lib/ldclient-rb/redis_store.rb +0 -88
  67. data/lib/ldclient-rb/user_filter.rb +0 -52
@@ -16,14 +16,14 @@ module LaunchDarkly
16
16
  end
17
17
 
18
18
  def initialize(opts)
19
- if !CONSUL_ENABLED
19
+ unless CONSUL_ENABLED
20
20
  raise RuntimeError.new("can't use Consul feature store without the 'diplomat' gem")
21
21
  end
22
22
 
23
23
  @prefix = (opts[:prefix] || LaunchDarkly::Integrations::Consul.default_prefix) + '/'
24
24
  @logger = opts[:logger] || Config.default_logger
25
- Diplomat.configuration = opts[:consul_config] if !opts[:consul_config].nil?
26
- Diplomat.configuration.url = opts[:url] if !opts[:url].nil?
25
+ Diplomat.configuration = opts[:consul_config] unless opts[:consul_config].nil?
26
+ Diplomat.configuration.url = opts[:url] unless opts[:url].nil?
27
27
  @logger.info("ConsulFeatureStore: using Consul host at #{Diplomat.configuration.url}")
28
28
  end
29
29
 
@@ -51,10 +51,10 @@ module LaunchDarkly
51
51
  unused_old_keys.each do |key|
52
52
  ops.push({ 'KV' => { 'Verb' => 'delete', 'Key' => key } })
53
53
  end
54
-
54
+
55
55
  # Now set the special key that we check in initialized_internal?
56
56
  ops.push({ 'KV' => { 'Verb' => 'set', 'Key' => inited_key, 'Value' => '' } })
57
-
57
+
58
58
  ConsulUtil.batch_operations(ops)
59
59
 
60
60
  @logger.info { "Initialized database with #{num_items} items" }
@@ -70,7 +70,7 @@ module LaunchDarkly
70
70
  results = Diplomat::Kv.get(kind_key(kind), { recurse: true }, :return)
71
71
  (results == "" ? [] : results).each do |result|
72
72
  value = result[:value]
73
- if !value.nil?
73
+ unless value.nil?
74
74
  item = Model.deserialize(kind, value)
75
75
  items_out[item[:key].to_sym] = item
76
76
  end
@@ -119,6 +119,18 @@ module LaunchDarkly
119
119
  end
120
120
  end
121
121
 
122
+ def available?
123
+ # Most implementations use the initialized_internal? method as a
124
+ # proxy for this check. However, since `initialized_internal?`
125
+ # catches a KeyNotFound exception, and that exception can be raised
126
+ # when the server goes away, we have to modify our behavior
127
+ # slightly.
128
+ Diplomat::Kv.get(inited_key, {}, :return, :return)
129
+ true
130
+ rescue
131
+ false
132
+ end
133
+
122
134
  def stop
123
135
  # There's no Consul client instance to dispose of
124
136
  end
@@ -132,7 +144,7 @@ module LaunchDarkly
132
144
  def kind_key(kind)
133
145
  @prefix + kind[:namespace] + '/'
134
146
  end
135
-
147
+
136
148
  def inited_key
137
149
  @prefix + '$inited'
138
150
  end
@@ -16,28 +16,28 @@ module LaunchDarkly
16
16
  AWS_SDK_ENABLED = false
17
17
  end
18
18
  end
19
-
19
+
20
20
  PARTITION_KEY = "namespace"
21
21
  SORT_KEY = "key"
22
22
 
23
23
  def initialize(table_name, opts)
24
- if !AWS_SDK_ENABLED
24
+ unless AWS_SDK_ENABLED
25
25
  raise RuntimeError.new("can't use #{description} without the aws-sdk or aws-sdk-dynamodb gem")
26
26
  end
27
-
27
+
28
28
  @table_name = table_name
29
29
  @prefix = opts[:prefix] ? (opts[:prefix] + ":") : ""
30
30
  @logger = opts[:logger] || Config.default_logger
31
-
31
+
32
32
  if !opts[:existing_client].nil?
33
33
  @client = opts[:existing_client]
34
34
  else
35
35
  @client = Aws::DynamoDB::Client.new(opts[:dynamodb_opts] || {})
36
36
  end
37
-
38
- @logger.info("${description}: using DynamoDB table \"#{table_name}\"")
37
+
38
+ @logger.info("#{description}: using DynamoDB table \"#{table_name}\"")
39
39
  end
40
-
40
+
41
41
  def stop
42
42
  # AWS client doesn't seem to have a close method
43
43
  end
@@ -46,7 +46,7 @@ module LaunchDarkly
46
46
  "DynamoDB"
47
47
  end
48
48
  end
49
-
49
+
50
50
  #
51
51
  # Internal implementation of the DynamoDB feature store, intended to be used with CachingStoreWrapper.
52
52
  #
@@ -62,6 +62,14 @@ module LaunchDarkly
62
62
  "DynamoDBFeatureStore"
63
63
  end
64
64
 
65
+ def available?
66
+ resp = get_item_by_keys(inited_key, inited_key)
67
+ !resp.item.nil? && resp.item.length > 0
68
+ true
69
+ rescue
70
+ false
71
+ end
72
+
65
73
  def init_internal(all_data)
66
74
  # Start by reading the existing keys; we will later delete any of these that weren't in all_data.
67
75
  unused_old_keys = read_existing_keys(all_data.keys)
@@ -83,7 +91,7 @@ module LaunchDarkly
83
91
  del_item = make_keys_hash(tuple[0], tuple[1])
84
92
  requests.push({ delete_request: { key: del_item } })
85
93
  end
86
-
94
+
87
95
  # Now set the special key that we check in initialized_internal?
88
96
  inited_item = make_keys_hash(inited_key, inited_key)
89
97
  requests.push({ put_request: { item: inited_item } })
@@ -123,11 +131,11 @@ module LaunchDarkly
123
131
  expression_attribute_names: {
124
132
  "#namespace" => PARTITION_KEY,
125
133
  "#key" => SORT_KEY,
126
- "#version" => VERSION_ATTRIBUTE
134
+ "#version" => VERSION_ATTRIBUTE,
127
135
  },
128
136
  expression_attribute_values: {
129
- ":version" => new_item[:version]
130
- }
137
+ ":version" => new_item[:version],
138
+ },
131
139
  })
132
140
  new_item
133
141
  rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException
@@ -159,7 +167,7 @@ module LaunchDarkly
159
167
  def make_keys_hash(namespace, key)
160
168
  {
161
169
  PARTITION_KEY => namespace,
162
- SORT_KEY => key
170
+ SORT_KEY => key,
163
171
  }
164
172
  end
165
173
 
@@ -170,16 +178,16 @@ module LaunchDarkly
170
178
  key_conditions: {
171
179
  PARTITION_KEY => {
172
180
  comparison_operator: "EQ",
173
- attribute_value_list: [ namespace_for_kind(kind) ]
174
- }
175
- }
181
+ attribute_value_list: [ namespace_for_kind(kind) ],
182
+ },
183
+ },
176
184
  }
177
185
  end
178
186
 
179
187
  def get_item_by_keys(namespace, key)
180
188
  @client.get_item({
181
189
  table_name: @table_name,
182
- key: make_keys_hash(namespace, key)
190
+ key: make_keys_hash(namespace, key),
183
191
  })
184
192
  end
185
193
 
@@ -190,8 +198,8 @@ module LaunchDarkly
190
198
  projection_expression: "#namespace, #key",
191
199
  expression_attribute_names: {
192
200
  "#namespace" => PARTITION_KEY,
193
- "#key" => SORT_KEY
194
- }
201
+ "#key" => SORT_KEY,
202
+ },
195
203
  })
196
204
  while true
197
205
  resp = @client.query(req)
@@ -210,7 +218,7 @@ module LaunchDarkly
210
218
  def marshal_item(kind, item)
211
219
  make_keys_hash(namespace_for_kind(kind), item[:key]).merge({
212
220
  VERSION_ATTRIBUTE => item[:version],
213
- ITEM_JSON_ATTRIBUTE => Model.serialize(kind, item)
221
+ ITEM_JSON_ATTRIBUTE => Model.serialize(kind, item),
214
222
  })
215
223
  end
216
224
 
@@ -223,11 +231,11 @@ module LaunchDarkly
223
231
  end
224
232
 
225
233
  class DynamoDBBigSegmentStore < DynamoDBStoreImplBase
226
- KEY_METADATA = 'big_segments_metadata';
227
- KEY_USER_DATA = 'big_segments_user';
228
- ATTR_SYNC_TIME = 'synchronizedOn';
229
- ATTR_INCLUDED = 'included';
230
- ATTR_EXCLUDED = 'excluded';
234
+ KEY_METADATA = 'big_segments_metadata'
235
+ KEY_CONTEXT_DATA = 'big_segments_user'
236
+ ATTR_SYNC_TIME = 'synchronizedOn'
237
+ ATTR_INCLUDED = 'included'
238
+ ATTR_EXCLUDED = 'excluded'
231
239
 
232
240
  def initialize(table_name, opts)
233
241
  super(table_name, opts)
@@ -243,7 +251,7 @@ module LaunchDarkly
243
251
  table_name: @table_name,
244
252
  key: {
245
253
  PARTITION_KEY => key,
246
- SORT_KEY => key
254
+ SORT_KEY => key,
247
255
  }
248
256
  )
249
257
  timestamp = data.item && data.item[ATTR_SYNC_TIME] ?
@@ -251,14 +259,14 @@ module LaunchDarkly
251
259
  LaunchDarkly::Interfaces::BigSegmentStoreMetadata.new(timestamp)
252
260
  end
253
261
 
254
- def get_membership(user_hash)
262
+ def get_membership(context_hash)
255
263
  data = @client.get_item(
256
264
  table_name: @table_name,
257
265
  key: {
258
- PARTITION_KEY => @prefix + KEY_USER_DATA,
259
- SORT_KEY => user_hash
266
+ PARTITION_KEY => @prefix + KEY_CONTEXT_DATA,
267
+ SORT_KEY => context_hash,
260
268
  })
261
- return nil if !data.item
269
+ return nil unless data.item
262
270
  excluded_refs = data.item[ATTR_EXCLUDED] || []
263
271
  included_refs = data.item[ATTR_INCLUDED] || []
264
272
  if excluded_refs.empty? && included_refs.empty?
@@ -18,10 +18,18 @@ module LaunchDarkly
18
18
  require 'listen'
19
19
  @@have_listen = true
20
20
  rescue LoadError
21
+ # Ignored
21
22
  end
22
23
 
23
- def initialize(feature_store, logger, options={})
24
- @feature_store = feature_store
24
+ #
25
+ # @param data_store [LaunchDarkly::Interfaces::FeatureStore]
26
+ # @param data_source_update_sink [LaunchDarkly::Interfaces::DataSource::UpdateSink, nil] Might be nil for backwards compatibility reasons.
27
+ # @param logger [Logger]
28
+ # @param options [Hash]
29
+ #
30
+ def initialize(data_store, data_source_update_sink, logger, options={})
31
+ @data_store = data_source_update_sink || data_store
32
+ @data_source_update_sink = data_source_update_sink
25
33
  @logger = logger
26
34
  @paths = options[:paths] || []
27
35
  if @paths.is_a? String
@@ -48,7 +56,7 @@ module LaunchDarkly
48
56
 
49
57
  def start
50
58
  ready = Concurrent::Event.new
51
-
59
+
52
60
  # We will return immediately regardless of whether the file load succeeded or failed -
53
61
  # the difference can be detected by checking "initialized?"
54
62
  ready.set
@@ -63,9 +71,9 @@ module LaunchDarkly
63
71
 
64
72
  ready
65
73
  end
66
-
74
+
67
75
  def stop
68
- @listener.stop if !@listener.nil?
76
+ @listener.stop unless @listener.nil?
69
77
  end
70
78
 
71
79
  private
@@ -73,17 +81,22 @@ module LaunchDarkly
73
81
  def load_all
74
82
  all_data = {
75
83
  FEATURES => {},
76
- SEGMENTS => {}
84
+ SEGMENTS => {},
77
85
  }
78
86
  @paths.each do |path|
79
87
  begin
80
88
  load_file(path, all_data)
81
89
  rescue => exn
82
90
  LaunchDarkly::Util.log_exception(@logger, "Unable to load flag data from \"#{path}\"", exn)
91
+ @data_source_update_sink&.update_status(
92
+ LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
93
+ LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(LaunchDarkly::Interfaces::DataSource::ErrorInfo::INVALID_DATA, 0, exn.to_s, Time.now)
94
+ )
83
95
  return
84
96
  end
85
97
  end
86
- @feature_store.init(all_data)
98
+ @data_store.init(all_data)
99
+ @data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::VALID, nil)
87
100
  @initialized.make_true
88
101
  end
89
102
 
@@ -121,12 +134,12 @@ module LaunchDarkly
121
134
 
122
135
  def add_item(all_data, kind, item)
123
136
  items = all_data[kind]
124
- raise ArgumentError, "Received unknown item kind #{kind} in add_data" if items.nil? # shouldn't be possible since we preinitialize the hash
137
+ raise ArgumentError, "Received unknown item kind #{kind[:namespace]} in add_data" if items.nil? # shouldn't be possible since we preinitialize the hash
125
138
  key = item[:key].to_sym
126
- if !items[key].nil?
139
+ unless items[key].nil?
127
140
  raise ArgumentError, "#{kind[:namespace]} key \"#{item[:key]}\" was used more than once"
128
141
  end
129
- items[key] = item
142
+ items[key] = Model.deserialize(kind, item)
130
143
  end
131
144
 
132
145
  def make_flag_with_value(key, value)
@@ -134,7 +147,7 @@ module LaunchDarkly
134
147
  key: key,
135
148
  on: true,
136
149
  fallthrough: { variation: 0 },
137
- variations: [ value ]
150
+ variations: [ value ],
138
151
  }
139
152
  end
140
153
 
@@ -1,3 +1,4 @@
1
+ require "ldclient-rb/interfaces"
1
2
  require "concurrent/atomics"
2
3
  require "json"
3
4
 
@@ -5,6 +6,95 @@ module LaunchDarkly
5
6
  module Impl
6
7
  module Integrations
7
8
  module Redis
9
+ #
10
+ # An implementation of the LaunchDarkly client's feature store that uses a Redis
11
+ # instance. This object holds feature flags and related data received from the
12
+ # streaming API. Feature data can also be further cached in memory to reduce overhead
13
+ # of calls to Redis.
14
+ #
15
+ # To use this class, you must first have the `redis` and `connection-pool` gems
16
+ # installed. Then, create an instance and store it in the `feature_store` property
17
+ # of your client configuration.
18
+ #
19
+ class RedisFeatureStore
20
+ include LaunchDarkly::Interfaces::FeatureStore
21
+
22
+ # Note that this class is now just a facade around CachingStoreWrapper, which is in turn delegating
23
+ # to RedisFeatureStoreCore where the actual database logic is. This class was retained for historical
24
+ # reasons, so that existing code can still call RedisFeatureStore.new. In the future, we will migrate
25
+ # away from exposing these concrete classes and use factory methods instead.
26
+
27
+ #
28
+ # Constructor for a RedisFeatureStore instance.
29
+ #
30
+ # @param opts [Hash] the configuration options
31
+ # @option opts [String] :redis_url URL of the Redis instance (shortcut for omitting redis_opts)
32
+ # @option opts [Hash] :redis_opts options to pass to the Redis constructor (if you want to specify more than just redis_url)
33
+ # @option opts [String] :prefix namespace prefix to add to all hash keys used by LaunchDarkly
34
+ # @option opts [Logger] :logger a `Logger` instance; defaults to `Config.default_logger`
35
+ # @option opts [Integer] :max_connections size of the Redis connection pool
36
+ # @option opts [Integer] :expiration expiration time for the in-memory cache, in seconds; 0 for no local caching
37
+ # @option opts [Integer] :capacity maximum number of feature flags (or related objects) to cache locally
38
+ # @option opts [Object] :pool custom connection pool, if desired
39
+ # @option opts [Boolean] :pool_shutdown_on_close whether calling `close` should shutdown the custom connection pool.
40
+ #
41
+ def initialize(opts = {})
42
+ core = RedisFeatureStoreCore.new(opts)
43
+ @wrapper = LaunchDarkly::Integrations::Util::CachingStoreWrapper.new(core, opts)
44
+ end
45
+
46
+ def monitoring_enabled?
47
+ true
48
+ end
49
+
50
+ def available?
51
+ @wrapper.available?
52
+ end
53
+
54
+ #
55
+ # Default value for the `redis_url` constructor parameter; points to an instance of Redis
56
+ # running at `localhost` with its default port.
57
+ #
58
+ def self.default_redis_url
59
+ LaunchDarkly::Integrations::Redis::default_redis_url
60
+ end
61
+
62
+ #
63
+ # Default value for the `prefix` constructor parameter.
64
+ #
65
+ def self.default_prefix
66
+ LaunchDarkly::Integrations::Redis::default_prefix
67
+ end
68
+
69
+ def get(kind, key)
70
+ @wrapper.get(kind, key)
71
+ end
72
+
73
+ def all(kind)
74
+ @wrapper.all(kind)
75
+ end
76
+
77
+ def delete(kind, key, version)
78
+ @wrapper.delete(kind, key, version)
79
+ end
80
+
81
+ def init(all_data)
82
+ @wrapper.init(all_data)
83
+ end
84
+
85
+ def upsert(kind, item)
86
+ @wrapper.upsert(kind, item)
87
+ end
88
+
89
+ def initialized?
90
+ @wrapper.initialized?
91
+ end
92
+
93
+ def stop
94
+ @wrapper.stop
95
+ end
96
+ end
97
+
8
98
  class RedisStoreImplBase
9
99
  begin
10
100
  require "redis"
@@ -15,7 +105,7 @@ module LaunchDarkly
15
105
  end
16
106
 
17
107
  def initialize(opts)
18
- if !REDIS_ENABLED
108
+ unless REDIS_ENABLED
19
109
  raise RuntimeError.new("can't use #{description} because one of these gems is missing: redis, connection_pool")
20
110
  end
21
111
 
@@ -28,7 +118,7 @@ module LaunchDarkly
28
118
  @logger = opts[:logger] || Config.default_logger
29
119
  @test_hook = opts[:test_hook] # used for unit tests, deliberately undocumented
30
120
 
31
- @stopped = Concurrent::AtomicBoolean.new(false)
121
+ @stopped = Concurrent::AtomicBoolean.new
32
122
 
33
123
  with_connection do |redis|
34
124
  @logger.info("#{description}: using Redis instance at #{redis.connection[:host]}:#{redis.connection[:port]} and prefix: #{@prefix}")
@@ -55,13 +145,11 @@ module LaunchDarkly
55
145
  if opts[:redis_url]
56
146
  redis_opts[:url] = opts[:redis_url]
57
147
  end
58
- if !redis_opts.include?(:url)
148
+ unless redis_opts.include?(:url)
59
149
  redis_opts[:url] = LaunchDarkly::Integrations::Redis::default_redis_url
60
150
  end
61
151
  max_connections = opts[:max_connections] || 16
62
- return opts[:pool] || ConnectionPool.new(size: max_connections) do
63
- ::Redis.new(redis_opts)
64
- end
152
+ opts[:pool] || ConnectionPool.new(size: max_connections) { ::Redis.new(redis_opts) }
65
153
  end
66
154
  end
67
155
 
@@ -75,6 +163,14 @@ module LaunchDarkly
75
163
  @test_hook = opts[:test_hook] # used for unit tests, deliberately undocumented
76
164
  end
77
165
 
166
+ def available?
167
+ # We don't care what the status is, only that we can connect
168
+ initialized_internal?
169
+ true
170
+ rescue
171
+ false
172
+ end
173
+
78
174
  def description
79
175
  "RedisFeatureStore"
80
176
  end
@@ -135,6 +231,7 @@ module LaunchDarkly
135
231
  else
136
232
  final_item = old_item
137
233
  action = new_item[:deleted] ? "delete" : "update"
234
+ # rubocop:disable Layout/LineLength
138
235
  @logger.warn { "RedisFeatureStore: attempted to #{action} #{key} version: #{old_item[:version]} in '#{kind[:namespace]}' with a version that is the same or older: #{new_item[:version]}" }
139
236
  end
140
237
  redis.unwatch
@@ -151,7 +248,7 @@ module LaunchDarkly
151
248
  private
152
249
 
153
250
  def before_update_transaction(base_key, key)
154
- @test_hook.before_update_transaction(base_key, key) if !@test_hook.nil?
251
+ @test_hook.before_update_transaction(base_key, key) unless @test_hook.nil?
155
252
  end
156
253
 
157
254
  def items_key(kind)
@@ -176,8 +273,8 @@ module LaunchDarkly
176
273
  #
177
274
  class RedisBigSegmentStore < RedisStoreImplBase
178
275
  KEY_LAST_UP_TO_DATE = ':big_segments_synchronized_on'
179
- KEY_USER_INCLUDE = ':big_segment_include:'
180
- KEY_USER_EXCLUDE = ':big_segment_exclude:'
276
+ KEY_CONTEXT_INCLUDE = ':big_segment_include:'
277
+ KEY_CONTEXT_EXCLUDE = ':big_segment_exclude:'
181
278
 
182
279
  def description
183
280
  "RedisBigSegmentStore"
@@ -188,10 +285,10 @@ module LaunchDarkly
188
285
  Interfaces::BigSegmentStoreMetadata.new(value.nil? ? nil : value.to_i)
189
286
  end
190
287
 
191
- def get_membership(user_hash)
288
+ def get_membership(context_hash)
192
289
  with_connection do |redis|
193
- included_refs = redis.smembers(@prefix + KEY_USER_INCLUDE + user_hash)
194
- excluded_refs = redis.smembers(@prefix + KEY_USER_EXCLUDE + user_hash)
290
+ included_refs = redis.smembers(@prefix + KEY_CONTEXT_INCLUDE + context_hash)
291
+ excluded_refs = redis.smembers(@prefix + KEY_CONTEXT_EXCLUDE + context_hash)
195
292
  if !included_refs && !excluded_refs
196
293
  nil
197
294
  else