launchdarkly-server-sdk 6.2.5 → 6.3.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.
@@ -1,55 +1,6 @@
1
1
  require "ldclient-rb/integrations/consul"
2
2
  require "ldclient-rb/integrations/dynamodb"
3
+ require "ldclient-rb/integrations/file_data"
3
4
  require "ldclient-rb/integrations/redis"
5
+ require "ldclient-rb/integrations/test_data"
4
6
  require "ldclient-rb/integrations/util/store_wrapper"
5
-
6
- module LaunchDarkly
7
- #
8
- # Tools for connecting the LaunchDarkly client to other software.
9
- #
10
- module Integrations
11
- #
12
- # Integration with [Consul](https://www.consul.io/).
13
- #
14
- # Note that in order to use this integration, you must first install the gem `diplomat`.
15
- #
16
- # @since 5.5.0
17
- #
18
- module Consul
19
- # code is in ldclient-rb/impl/integrations/consul_impl
20
- end
21
-
22
- #
23
- # Integration with [DynamoDB](https://aws.amazon.com/dynamodb/).
24
- #
25
- # Note that in order to use this integration, you must first install one of the AWS SDK gems: either
26
- # `aws-sdk-dynamodb`, or the full `aws-sdk`.
27
- #
28
- # @since 5.5.0
29
- #
30
- module DynamoDB
31
- # code is in ldclient-rb/impl/integrations/dynamodb_impl
32
- end
33
-
34
- #
35
- # Integration with [Redis](https://redis.io/).
36
- #
37
- # Note that in order to use this integration, you must first install the `redis` and `connection-pool`
38
- # gems.
39
- #
40
- # @since 5.5.0
41
- #
42
- module Redis
43
- # code is in ldclient-rb/impl/integrations/redis_impl
44
- end
45
-
46
- #
47
- # Support code that may be helpful in creating integrations.
48
- #
49
- # @since 5.5.0
50
- #
51
- module Util
52
- # code is in ldclient-rb/integrations/util/
53
- end
54
- end
55
- end
@@ -1,3 +1,4 @@
1
+ require "observer"
1
2
 
2
3
  module LaunchDarkly
3
4
  #
@@ -120,7 +121,8 @@ module LaunchDarkly
120
121
  #
121
122
  # The client has its own standard implementation, which uses either a streaming connection or
122
123
  # polling depending on your configuration. Normally you will not need to use another one
123
- # except for testing purposes. {FileDataSource} provides one such test fixture.
124
+ # except for testing purposes. Two such test fixtures are {LaunchDarkly::Integrations::FileData}
125
+ # and {LaunchDarkly::Integrations::TestData}.
124
126
  #
125
127
  module DataSource
126
128
  #
@@ -149,5 +151,153 @@ module LaunchDarkly
149
151
  def stop
150
152
  end
151
153
  end
154
+
155
+ module BigSegmentStore
156
+ #
157
+ # Returns information about the overall state of the store. This method will be called only
158
+ # when the SDK needs the latest state, so it should not be cached.
159
+ #
160
+ # @return [BigSegmentStoreMetadata]
161
+ #
162
+ def get_metadata
163
+ end
164
+
165
+ #
166
+ # Queries the store for a snapshot of the current segment state for a specific user.
167
+ #
168
+ # The user_hash is a base64-encoded string produced by hashing the user key as defined by
169
+ # the Big Segments specification; the store implementation does not need to know the details
170
+ # of how this is done, because it deals only with already-hashed keys, but the string can be
171
+ # assumed to only contain characters that are valid in base64.
172
+ #
173
+ # The return value should be either a Hash, or nil if the user is not referenced in any big
174
+ # segments. Each key in the Hash is a "segment reference", which is how segments are
175
+ # identified in Big Segment data. This string is not identical to the segment key-- the SDK
176
+ # will add other information. The store implementation should not be concerned with the
177
+ # format of the string. Each value in the Hash is true if the user is explicitly included in
178
+ # the segment, false if the user is explicitly excluded from the segment-- and is not also
179
+ # explicitly included (that is, if both an include and an exclude existed in the data, the
180
+ # include would take precedence). If the user's status in a particular segment is undefined,
181
+ # there should be no key or value for that segment.
182
+ #
183
+ # This Hash may be cached by the SDK, so it should not be modified after it is created. It
184
+ # is a snapshot of the segment membership state at one point in time.
185
+ #
186
+ # @param user_hash [String]
187
+ # @return [Hash] true/false values for Big Segments that reference this user
188
+ #
189
+ def get_membership(user_hash)
190
+ end
191
+
192
+ #
193
+ # Performs any necessary cleanup to shut down the store when the client is being shut down.
194
+ #
195
+ # @return [void]
196
+ #
197
+ def stop
198
+ end
199
+ end
200
+
201
+ #
202
+ # Values returned by {BigSegmentStore#get_metadata}.
203
+ #
204
+ class BigSegmentStoreMetadata
205
+ def initialize(last_up_to_date)
206
+ @last_up_to_date = last_up_to_date
207
+ end
208
+
209
+ # The Unix epoch millisecond timestamp of the last update to the {BigSegmentStore}. It is
210
+ # nil if the store has never been updated.
211
+ #
212
+ # @return [Integer|nil]
213
+ attr_reader :last_up_to_date
214
+ end
215
+
216
+ #
217
+ # Information about the status of a Big Segment store, provided by {BigSegmentStoreStatusProvider}.
218
+ #
219
+ # Big Segments are a specific type of user segments. For more information, read the LaunchDarkly
220
+ # documentation: https://docs.launchdarkly.com/home/users/big-segments
221
+ #
222
+ class BigSegmentStoreStatus
223
+ def initialize(available, stale)
224
+ @available = available
225
+ @stale = stale
226
+ end
227
+
228
+ # True if the Big Segment store is able to respond to queries, so that the SDK can evaluate
229
+ # whether a user is in a segment or not.
230
+ #
231
+ # If this property is false, the store is not able to make queries (for instance, it may not have
232
+ # a valid database connection). In this case, the SDK will treat any reference to a Big Segment
233
+ # as if no users are included in that segment. Also, the {EvaluationReason} associated with
234
+ # with any flag evaluation that references a Big Segment when the store is not available will
235
+ # have a `big_segments_status` of `STORE_ERROR`.
236
+ #
237
+ # @return [Boolean]
238
+ attr_reader :available
239
+
240
+ # True if the Big Segment store is available, but has not been updated within the amount of time
241
+ # specified by {BigSegmentsConfig#stale_after}.
242
+ #
243
+ # This may indicate that the LaunchDarkly Relay Proxy, which populates the store, has stopped
244
+ # running or has become unable to receive fresh data from LaunchDarkly. Any feature flag
245
+ # evaluations that reference a Big Segment will be using the last known data, which may be out
246
+ # of date. Also, the {EvaluationReason} associated with those evaluations will have a
247
+ # `big_segments_status` of `STALE`.
248
+ #
249
+ # @return [Boolean]
250
+ attr_reader :stale
251
+
252
+ def ==(other)
253
+ self.available == other.available && self.stale == other.stale
254
+ end
255
+ end
256
+
257
+ #
258
+ # An interface for querying the status of a Big Segment store.
259
+ #
260
+ # The Big Segment store is the component that receives information about Big Segments, normally
261
+ # from a database populated by the LaunchDarkly Relay Proxy. Big Segments are a specific type
262
+ # of user segments. For more information, read the LaunchDarkly documentation:
263
+ # https://docs.launchdarkly.com/home/users/big-segments
264
+ #
265
+ # An implementation of this interface is returned by {LDClient#big_segment_store_status_provider}.
266
+ # Application code never needs to implement this interface.
267
+ #
268
+ # There are two ways to interact with the status. One is to simply get the current status; if its
269
+ # `available` property is true, then the SDK is able to evaluate user membership in Big Segments,
270
+ # and the `stale`` property indicates whether the data might be out of date.
271
+ #
272
+ # The other way is to subscribe to status change notifications. Applications may wish to know if
273
+ # there is an outage in the Big Segment store, or if it has become stale (the Relay Proxy has
274
+ # stopped updating it with new data), since then flag evaluations that reference a Big Segment
275
+ # might return incorrect values. To allow finding out about status changes as soon as possible,
276
+ # `BigSegmentStoreStatusProvider` mixes in Ruby's
277
+ # [Observable](https://docs.ruby-lang.org/en/2.5.0/Observable.html) module to provide standard
278
+ # methods such as `add_observer`. Observers will be called with a new {BigSegmentStoreStatus}
279
+ # value whenever the status changes.
280
+ #
281
+ # @example Getting the current status
282
+ # status = client.big_segment_store_status_provider.status
283
+ #
284
+ # @example Subscribing to status notifications
285
+ # client.big_segment_store_status_provider.add_observer(self, :big_segments_status_changed)
286
+ #
287
+ # def big_segments_status_changed(new_status)
288
+ # puts "Big segment store status is now: #{new_status}"
289
+ # end
290
+ #
291
+ module BigSegmentStoreStatusProvider
292
+ include Observable
293
+ #
294
+ # Gets the current status of the store, if known.
295
+ #
296
+ # @return [BigSegmentStoreStatus] the status, or nil if the SDK has not yet queried the Big
297
+ # Segment store status
298
+ #
299
+ def status
300
+ end
301
+ end
152
302
  end
153
303
  end
@@ -1,3 +1,4 @@
1
+ require "ldclient-rb/impl/big_segments"
1
2
  require "ldclient-rb/impl/diagnostic_events"
2
3
  require "ldclient-rb/impl/evaluator"
3
4
  require "ldclient-rb/impl/event_factory"
@@ -57,10 +58,14 @@ module LaunchDarkly
57
58
  updated_config.instance_variable_set(:@feature_store, @store)
58
59
  @config = updated_config
59
60
 
61
+ @big_segment_store_manager = Impl::BigSegmentStoreManager.new(config.big_segments, @config.logger)
62
+ @big_segment_store_status_provider = @big_segment_store_manager.status_provider
63
+
60
64
  get_flag = lambda { |key| @store.get(FEATURES, key) }
61
65
  get_segment = lambda { |key| @store.get(SEGMENTS, key) }
62
- @evaluator = LaunchDarkly::Impl::Evaluator.new(get_flag, get_segment, @config.logger)
63
-
66
+ get_big_segments_membership = lambda { |key| @big_segment_store_manager.get_user_membership(key) }
67
+ @evaluator = LaunchDarkly::Impl::Evaluator.new(get_flag, get_segment, get_big_segments_membership, @config.logger)
68
+
64
69
  if !@config.offline? && @config.send_events && !@config.diagnostic_opt_out?
65
70
  diagnostic_accumulator = Impl::DiagnosticAccumulator.new(Impl::DiagnosticAccumulator.create_diagnostic_id(sdk_key))
66
71
  else
@@ -375,9 +380,18 @@ module LaunchDarkly
375
380
  @config.logger.info { "[LDClient] Closing LaunchDarkly client..." }
376
381
  @data_source.stop
377
382
  @event_processor.stop
383
+ @big_segment_store_manager.stop
378
384
  @store.stop
379
385
  end
380
386
 
387
+ #
388
+ # Returns an interface for tracking the status of a Big Segment store.
389
+ #
390
+ # The {BigSegmentStoreStatusProvider} has methods for checking whether the Big Segment store
391
+ # is (as far as the SDK knows) currently operational and tracking changes in this status.
392
+ #
393
+ attr_reader :big_segment_store_status_provider
394
+
381
395
  private
382
396
 
383
397
  def create_default_data_source(sdk_key, config, diagnostic_accumulator)
@@ -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
- create_worker
25
+ @task.start
24
26
  @ready
25
27
  end
26
28
 
27
29
  def stop
28
- if @stopped.make_true
29
- if @worker && @worker.alive? && @worker != Thread.current
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
- all_data = @requestor.request_all_data
39
- if all_data
40
- @config.feature_store.init(all_data)
41
- if @initialized.make_true
42
- @config.logger.info { "[LDClient] Polling connection initialized" }
43
- @ready.set
44
- end
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
+ if !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
@@ -18,7 +18,7 @@ module LaunchDarkly
18
18
  end
19
19
  ret
20
20
  end
21
-
21
+
22
22
  def self.new_http_client(uri_s, config)
23
23
  http_client_options = {}
24
24
  if config.socket_factory
@@ -1,3 +1,3 @@
1
1
  module LaunchDarkly
2
- VERSION = "6.2.5"
2
+ VERSION = "6.3.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: launchdarkly-server-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.2.5
4
+ version: 6.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - LaunchDarkly
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-10-12 00:00:00.000000000 Z
11
+ date: 2021-12-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk-dynamodb
@@ -260,6 +260,7 @@ files:
260
260
  - lib/ldclient-rb/file_data_source.rb
261
261
  - lib/ldclient-rb/flags_state.rb
262
262
  - lib/ldclient-rb/impl.rb
263
+ - lib/ldclient-rb/impl/big_segments.rb
263
264
  - lib/ldclient-rb/impl/diagnostic_events.rb
264
265
  - lib/ldclient-rb/impl/evaluator.rb
265
266
  - lib/ldclient-rb/impl/evaluator_bucketing.rb
@@ -268,8 +269,11 @@ files:
268
269
  - lib/ldclient-rb/impl/event_sender.rb
269
270
  - lib/ldclient-rb/impl/integrations/consul_impl.rb
270
271
  - lib/ldclient-rb/impl/integrations/dynamodb_impl.rb
272
+ - lib/ldclient-rb/impl/integrations/file_data_source.rb
271
273
  - lib/ldclient-rb/impl/integrations/redis_impl.rb
274
+ - lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb
272
275
  - lib/ldclient-rb/impl/model/serialization.rb
276
+ - lib/ldclient-rb/impl/repeating_task.rb
273
277
  - lib/ldclient-rb/impl/store_client_wrapper.rb
274
278
  - lib/ldclient-rb/impl/store_data_set_sorter.rb
275
279
  - lib/ldclient-rb/impl/unbounded_pool.rb
@@ -278,7 +282,10 @@ files:
278
282
  - lib/ldclient-rb/integrations.rb
279
283
  - lib/ldclient-rb/integrations/consul.rb
280
284
  - lib/ldclient-rb/integrations/dynamodb.rb
285
+ - lib/ldclient-rb/integrations/file_data.rb
281
286
  - lib/ldclient-rb/integrations/redis.rb
287
+ - lib/ldclient-rb/integrations/test_data.rb
288
+ - lib/ldclient-rb/integrations/test_data/flag_builder.rb
282
289
  - lib/ldclient-rb/integrations/util/store_wrapper.rb
283
290
  - lib/ldclient-rb/interfaces.rb
284
291
  - lib/ldclient-rb/ldclient.rb
@@ -312,7 +319,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
312
319
  - !ruby/object:Gem::Version
313
320
  version: '0'
314
321
  requirements: []
315
- rubygems_version: 3.2.29
322
+ rubygems_version: 3.2.33
316
323
  signing_key:
317
324
  specification_version: 4
318
325
  summary: LaunchDarkly SDK for Ruby