launchdarkly-server-sdk 6.3.0 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
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