launchdarkly-server-sdk 7.1.0 → 7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c7c60035c46d6e2284ad78f0f4e33637f804faf9c90229e09b9d7c5b64c3bf8
4
- data.tar.gz: 38f330a730ce6459d7a50bf0afec0db47462afafa7f927f09a2978ae3bd1cf18
3
+ metadata.gz: 9c039ad353413b88544bf8da89f1400af464323ce17c5677d3df96429fd1966c
4
+ data.tar.gz: 25eb62e52f75379f22a46df2273df1b7e6658b835f8f0470d03412ce55612467
5
5
  SHA512:
6
- metadata.gz: c15f778108d9fdc2d47b51846d068e553be322d4e072498368d9c8cc7161f2e15c8977ccc5dcf8e565a707d727029901bbd6e0a7780bb42c0407f21cbef959ec
7
- data.tar.gz: c7eca9b4edb6c458b1a034161ca9adaac955d81155308c23f913c434cf02a6479e15a7df35ea9ae4cf4d5e21b288c485a3ef1c380f90d6e4581e8993a7bc1e37
6
+ metadata.gz: 076b46271f1907f7a8a3bfcb322b1a291cb0aa2ed4ca3da822bf67dad9b023e1e162014e4d94350a72c0bd66af5101f9f2437950859d07e67d912e823ef02548
7
+ data.tar.gz: 8339b1e5393e366126227ffc00e4f3cf5de72a1f3fde013c501951bd7c19f3505c2ea69510d53b6df46f62599c2f44dab7e670ae5c05c2fdf5a6fccbaf0000d0
data/README.md CHANGED
@@ -9,7 +9,7 @@ LaunchDarkly Server-side SDK for Ruby
9
9
 
10
10
  LaunchDarkly overview
11
11
  -------------------------
12
- [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today!
12
+ [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves trillions of feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today!
13
13
 
14
14
  [![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly)
15
15
 
@@ -26,7 +26,7 @@ Refer to the [SDK documentation](https://docs.launchdarkly.com/sdk/server-side/r
26
26
  Learn more
27
27
  -----------
28
28
 
29
- Check out our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [reference guide for this SDK](http://docs.launchdarkly.com/docs/ruby-sdk-reference).
29
+ Read our [documentation](http://docs.launchdarkly.com) for in-depth instructions on configuring and using LaunchDarkly. You can also head straight to the [reference guide for this SDK](http://docs.launchdarkly.com/docs/ruby-sdk-reference).
30
30
 
31
31
  Generated API documentation for all versions of the SDK is on [RubyDoc.info](https://www.rubydoc.info/gems/launchdarkly-server-sdk). The API documentation for the latest version is also on [GitHub Pages](https://launchdarkly.github.io/ruby-server-sdk).
32
32
 
@@ -90,8 +90,23 @@ module LaunchDarkly
90
90
  @big_segments = opts[:big_segments] || BigSegmentsConfig.new(store: nil)
91
91
  @application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
92
92
  @payload_filter_key = opts[:payload_filter_key]
93
+ @data_source_update_sink = nil
93
94
  end
94
95
 
96
+ #
97
+ # Returns the component that allows a data source to push data into the SDK.
98
+ #
99
+ # This property should only be set by the SDK. Long term access of this
100
+ # property is not supported; it is temporarily being exposed to maintain
101
+ # backwards compatibility while the SDK structure is updated.
102
+ #
103
+ # Custom data source implementations should integrate with this sink if
104
+ # they want to provide support for data source status listeners.
105
+ #
106
+ # @private
107
+ #
108
+ attr_accessor :data_source_update_sink
109
+
95
110
  #
96
111
  # The base URL for the LaunchDarkly server. This is configurable mainly for testing
97
112
  # purposes; most users should use the default value.
@@ -471,7 +486,7 @@ module LaunchDarkly
471
486
 
472
487
  #
473
488
  # The default value for {#connect_timeout}.
474
- # @return [Float] 10
489
+ # @return [Float] 2
475
490
  #
476
491
  def self.default_connect_timeout
477
492
  2
@@ -317,6 +317,8 @@ module LaunchDarkly
317
317
  # {https://docs.launchdarkly.com/sdk/features/user-config SDK
318
318
  # documentation}.
319
319
  #
320
+ # @deprecated The old user format will be removed in 8.0.0. Please use the new context specific format.
321
+ #
320
322
  # @param data [Hash]
321
323
  # @return [LDContext]
322
324
  #
@@ -397,6 +399,8 @@ module LaunchDarkly
397
399
  # @return [LDContext]
398
400
  #
399
401
  private_class_method def self.create_legacy_context(data)
402
+ warn("DEPRECATED: legacy user format will be removed in 8.0.0", uplevel: 1)
403
+
400
404
  key = data[:key]
401
405
 
402
406
  # Legacy users are allowed to have "" as a key but they cannot have nil as a key.
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LaunchDarkly
4
+ module Impl
5
+
6
+ #
7
+ # A generic mechanism for registering event listeners and broadcasting
8
+ # events to them.
9
+ #
10
+ # The SDK maintains an instance of this for each available type of listener
11
+ # (flag change, data store status, etc.). They are all intended to share a
12
+ # single executor service; notifications are submitted individually to this
13
+ # service for each listener.
14
+ #
15
+ class Broadcaster
16
+ def initialize(executor, logger)
17
+ @listeners = Concurrent::Set.new
18
+ @executor = executor
19
+ @logger = logger
20
+ end
21
+
22
+ #
23
+ # Register a listener to this broadcaster.
24
+ #
25
+ # @param listener [#update]
26
+ #
27
+ def add_listener(listener)
28
+ unless listener.respond_to? :update
29
+ logger.warn("listener (#{listener.class}) does not respond to :update method. ignoring as registered listener")
30
+ return
31
+ end
32
+
33
+ listeners.add(listener)
34
+ end
35
+
36
+ #
37
+ # Removes a registered listener from this broadcaster.
38
+ #
39
+ def remove_listener(listener)
40
+ listeners.delete(listener)
41
+ end
42
+
43
+ def has_listeners?
44
+ !listeners.empty?
45
+ end
46
+
47
+ #
48
+ # Broadcast the provided event to all registered listeners.
49
+ #
50
+ # Each listener will be notified using the broadcasters executor. This
51
+ # method is non-blocking.
52
+ #
53
+ def broadcast(event)
54
+ listeners.each do |listener|
55
+ executor.post do
56
+ begin
57
+ listener.update(event)
58
+ rescue StandardError => e
59
+ logger.error("listener (#{listener.class}) raised exception (#{e}) processing event (#{event.class})")
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+
66
+ private
67
+
68
+ # @return [Concurrent::ThreadPoolExecutor]
69
+ attr_reader :executor
70
+
71
+ # @return [Logger]
72
+ attr_reader :logger
73
+
74
+ # @return [Concurrent::Set]
75
+ attr_reader :listeners
76
+ end
77
+ end
78
+ end
@@ -40,7 +40,7 @@ module LaunchDarkly
40
40
  return ERR_KIND_NON_STRING unless kind.is_a?(String)
41
41
  return ERR_KIND_CANNOT_BE_KIND if kind == "kind"
42
42
  return ERR_KIND_CANNOT_BE_MULTI if kind == "multi"
43
- return ERR_KIND_INVALID_CHARS unless kind.match?(/^[\w.-]+$/)
43
+ ERR_KIND_INVALID_CHARS unless kind.match?(/^[\w.-]+$/)
44
44
  end
45
45
 
46
46
  #
@@ -51,7 +51,7 @@ module LaunchDarkly
51
51
  #
52
52
  def self.validate_key(key)
53
53
  return ERR_KEY_NON_STRING unless key.is_a?(String)
54
- return ERR_KEY_EMPTY if key == ""
54
+ ERR_KEY_EMPTY if key == ""
55
55
  end
56
56
 
57
57
  #
@@ -61,7 +61,7 @@ module LaunchDarkly
61
61
  # @return [String, nil]
62
62
  #
63
63
  def self.validate_name(name)
64
- return ERR_NAME_NON_STRING unless name.nil? || name.is_a?(String)
64
+ ERR_NAME_NON_STRING unless name.nil? || name.is_a?(String)
65
65
  end
66
66
 
67
67
  #
@@ -0,0 +1,188 @@
1
+ require "concurrent"
2
+ require "forwardable"
3
+ require "ldclient-rb/impl/dependency_tracker"
4
+ require "ldclient-rb/interfaces"
5
+ require "set"
6
+
7
+ module LaunchDarkly
8
+ module Impl
9
+ module DataSource
10
+ class StatusProvider
11
+ include LaunchDarkly::Interfaces::DataSource::StatusProvider
12
+
13
+ extend Forwardable
14
+ def_delegators :@status_broadcaster, :add_listener, :remove_listener
15
+
16
+ def initialize(status_broadcaster, update_sink)
17
+ # @type [Broadcaster]
18
+ @status_broadcaster = status_broadcaster
19
+ # @type [UpdateSink]
20
+ @data_source_update_sink = update_sink
21
+ end
22
+
23
+ def status
24
+ @data_source_update_sink.current_status
25
+ end
26
+ end
27
+
28
+ class UpdateSink
29
+ include LaunchDarkly::Interfaces::DataSource::UpdateSink
30
+
31
+ # @return [LaunchDarkly::Interfaces::DataSource::Status]
32
+ attr_reader :current_status
33
+
34
+ def initialize(data_store, status_broadcaster, flag_change_broadcaster)
35
+ # @type [LaunchDarkly::Interfaces::FeatureStore]
36
+ @data_store = data_store
37
+ # @type [Broadcaster]
38
+ @status_broadcaster = status_broadcaster
39
+ # @type [Broadcaster]
40
+ @flag_change_broadcaster = flag_change_broadcaster
41
+ @dependency_tracker = LaunchDarkly::Impl::DependencyTracker.new
42
+
43
+ @mutex = Mutex.new
44
+ @current_status = LaunchDarkly::Interfaces::DataSource::Status.new(
45
+ LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
46
+ Time.now,
47
+ nil)
48
+ end
49
+
50
+ def init(all_data)
51
+ old_data = nil
52
+ monitor_store_update do
53
+ if @flag_change_broadcaster.has_listeners?
54
+ old_data = {}
55
+ LaunchDarkly::ALL_KINDS.each do |kind|
56
+ old_data[kind] = @data_store.all(kind)
57
+ end
58
+ end
59
+
60
+ @data_store.init(all_data)
61
+ end
62
+
63
+ update_full_dependency_tracker(all_data)
64
+
65
+ return if old_data.nil?
66
+
67
+ send_change_events(
68
+ compute_changed_items_for_full_data_set(old_data, all_data)
69
+ )
70
+ end
71
+
72
+ def upsert(kind, item)
73
+ monitor_store_update { @data_store.upsert(kind, item) }
74
+
75
+ # TODO(sc-197908): We only want to do this if the store successfully
76
+ # updates the record.
77
+ @dependency_tracker.update_dependencies_from(kind, item[:key], item)
78
+ if @flag_change_broadcaster.has_listeners?
79
+ affected_items = Set.new
80
+ @dependency_tracker.add_affected_items(affected_items, {kind: kind, key: item[:key]})
81
+ send_change_events(affected_items)
82
+ end
83
+ end
84
+
85
+ def delete(kind, key, version)
86
+ monitor_store_update { @data_store.delete(kind, key, version) }
87
+
88
+ @dependency_tracker.update_dependencies_from(kind, key, nil)
89
+ if @flag_change_broadcaster.has_listeners?
90
+ affected_items = Set.new
91
+ @dependency_tracker.add_affected_items(affected_items, {kind: kind, key: key})
92
+ send_change_events(affected_items)
93
+ end
94
+ end
95
+
96
+ def update_status(new_state, new_error)
97
+ return if new_state.nil?
98
+
99
+ status_to_broadcast = nil
100
+
101
+ @mutex.synchronize do
102
+ old_status = @current_status
103
+
104
+ if new_state == LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED && old_status.state == LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING
105
+ # See {LaunchDarkly::Interfaces::DataSource::UpdateSink#update_status} for more information
106
+ new_state = LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING
107
+ end
108
+
109
+ unless new_state == old_status.state && new_error.nil?
110
+ @current_status = LaunchDarkly::Interfaces::DataSource::Status.new(
111
+ new_state,
112
+ new_state == current_status.state ? current_status.state_since : Time.now,
113
+ new_error.nil? ? current_status.last_error : new_error
114
+ )
115
+ status_to_broadcast = current_status
116
+ end
117
+ end
118
+
119
+ @status_broadcaster.broadcast(status_to_broadcast) unless status_to_broadcast.nil?
120
+ end
121
+
122
+ private def update_full_dependency_tracker(all_data)
123
+ @dependency_tracker.reset
124
+ all_data.each do |kind, items|
125
+ items.each do |key, item|
126
+ @dependency_tracker.update_dependencies_from(kind, item.key, item)
127
+ end
128
+ end
129
+ end
130
+
131
+
132
+ #
133
+ # Method to monitor updates to the store. You provide a block to update
134
+ # the store. This mthod wraps that block, catching and re-raising all
135
+ # errors, and notifying all status listeners of the error.
136
+ #
137
+ private def monitor_store_update
138
+ begin
139
+ yield
140
+ rescue => e
141
+ error_info = LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(LaunchDarkly::Interfaces::DataSource::ErrorInfo::STORE_ERROR, 0, e.to_s, Time.now)
142
+ update_status(LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED, error_info)
143
+ raise
144
+ end
145
+ end
146
+
147
+
148
+ #
149
+ # @param [Hash] old_data
150
+ # @param [Hash] new_data
151
+ # @return [Set]
152
+ #
153
+ private def compute_changed_items_for_full_data_set(old_data, new_data)
154
+ affected_items = Set.new
155
+
156
+ LaunchDarkly::ALL_KINDS.each do |kind|
157
+ old_items = old_data[kind] || {}
158
+ new_items = new_data[kind] || {}
159
+
160
+ old_items.keys.concat(new_items.keys).each do |key|
161
+ old_item = old_items[key]
162
+ new_item = new_items[key]
163
+
164
+ next if old_item.nil? && new_item.nil?
165
+
166
+ if old_item.nil? || new_item.nil? || old_item[:version] < new_item[:version]
167
+ @dependency_tracker.add_affected_items(affected_items, {kind: kind, key: key.to_s})
168
+ end
169
+ end
170
+ end
171
+
172
+ affected_items
173
+ end
174
+
175
+ #
176
+ # @param affected_items [Set]
177
+ #
178
+ private def send_change_events(affected_items)
179
+ affected_items.each do |item|
180
+ if item[:kind] == LaunchDarkly::FEATURES
181
+ @flag_change_broadcaster.broadcast(LaunchDarkly::Interfaces::FlagChange.new(item[:key]))
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,59 @@
1
+ require 'concurrent'
2
+ require "ldclient-rb/interfaces"
3
+
4
+ module LaunchDarkly
5
+ module Impl
6
+ module DataStore
7
+ class StatusProvider
8
+ include LaunchDarkly::Interfaces::DataStore::StatusProvider
9
+
10
+ def initialize(store, update_sink)
11
+ # @type [LaunchDarkly::Impl::FeatureStoreClientWrapper]
12
+ @store = store
13
+ # @type [UpdateSink]
14
+ @update_sink = update_sink
15
+ end
16
+
17
+ def status
18
+ @update_sink.last_status.get
19
+ end
20
+
21
+ def monitoring_enabled?
22
+ @store.monitoring_enabled?
23
+ end
24
+
25
+ def add_listener(listener)
26
+ @update_sink.broadcaster.add_listener(listener)
27
+ end
28
+
29
+ def remove_listener(listener)
30
+ @update_sink.broadcaster.remove_listener(listener)
31
+ end
32
+ end
33
+
34
+ class UpdateSink
35
+ include LaunchDarkly::Interfaces::DataStore::UpdateSink
36
+
37
+ # @return [LaunchDarkly::Impl::Broadcaster]
38
+ attr_reader :broadcaster
39
+
40
+ # @return [Concurrent::AtomicReference]
41
+ attr_reader :last_status
42
+
43
+ def initialize(broadcaster)
44
+ @broadcaster = broadcaster
45
+ @last_status = Concurrent::AtomicReference.new(
46
+ LaunchDarkly::Interfaces::DataStore::Status.new(true, false)
47
+ )
48
+ end
49
+
50
+ def update_status(status)
51
+ return if status.nil?
52
+
53
+ old_status = @last_status.get_and_set(status)
54
+ @broadcaster.broadcast(status) unless old_status == status
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,102 @@
1
+ module LaunchDarkly
2
+ module Impl
3
+ class DependencyTracker
4
+ def initialize
5
+ @from = {}
6
+ @to = {}
7
+ end
8
+
9
+ #
10
+ # Updates the dependency graph when an item has changed.
11
+ #
12
+ # @param from_kind [Object] the changed item's kind
13
+ # @param from_key [String] the changed item's key
14
+ # @param from_item [Object] the changed item
15
+ #
16
+ def update_dependencies_from(from_kind, from_key, from_item)
17
+ from_what = { kind: from_kind, key: from_key }
18
+ updated_dependencies = DependencyTracker.compute_dependencies_from(from_kind, from_item)
19
+
20
+ old_dependency_set = @from[from_what]
21
+ unless old_dependency_set.nil?
22
+ old_dependency_set.each do |kind_and_key|
23
+ deps_to_this_old_dep = @to[kind_and_key]
24
+ deps_to_this_old_dep&.delete(from_what)
25
+ end
26
+ end
27
+
28
+ @from[from_what] = updated_dependencies
29
+ updated_dependencies.each do |kind_and_key|
30
+ deps_to_this_new_dep = @to[kind_and_key]
31
+ if deps_to_this_new_dep.nil?
32
+ deps_to_this_new_dep = Set.new
33
+ @to[kind_and_key] = deps_to_this_new_dep
34
+ end
35
+ deps_to_this_new_dep.add(from_what)
36
+ end
37
+ end
38
+
39
+ def self.segment_keys_from_clauses(clauses)
40
+ clauses.flat_map do |clause|
41
+ if clause.op == :segmentMatch
42
+ clause.values.map { |value| {kind: LaunchDarkly::SEGMENTS, key: value }}
43
+ else
44
+ []
45
+ end
46
+ end
47
+ end
48
+
49
+ #
50
+ # @param from_kind [String]
51
+ # @param from_item [LaunchDarkly::Impl::Model::FeatureFlag, LaunchDarkly::Impl::Model::Segment]
52
+ # @return [Set]
53
+ #
54
+ def self.compute_dependencies_from(from_kind, from_item)
55
+ return Set.new if from_item.nil?
56
+
57
+ if from_kind == LaunchDarkly::FEATURES
58
+ prereq_keys = from_item.prerequisites.map { |prereq| {kind: from_kind, key: prereq.key} }
59
+ segment_keys = from_item.rules.flat_map { |rule| DependencyTracker.segment_keys_from_clauses(rule.clauses) }
60
+
61
+ results = Set.new(prereq_keys)
62
+ results.merge(segment_keys)
63
+ elsif from_kind == LaunchDarkly::SEGMENTS
64
+ kind_and_keys = from_item.rules.flat_map do |rule|
65
+ DependencyTracker.segment_keys_from_clauses(rule.clauses)
66
+ end
67
+ Set.new(kind_and_keys)
68
+ else
69
+ Set.new
70
+ end
71
+ end
72
+
73
+ #
74
+ # Clear any tracked dependencies and reset the tracking state to a clean slate.
75
+ #
76
+ def reset
77
+ @from.clear
78
+ @to.clear
79
+ end
80
+
81
+ #
82
+ # Populates the given set with the union of the initial item and all items that directly or indirectly
83
+ # depend on it (based on the current state of the dependency graph).
84
+ #
85
+ # @param items_out [Set]
86
+ # @param initial_modified_item [Object]
87
+ #
88
+ def add_affected_items(items_out, initial_modified_item)
89
+ return if items_out.include? initial_modified_item
90
+
91
+ items_out.add(initial_modified_item)
92
+ affected_items = @to[initial_modified_item]
93
+
94
+ return if affected_items.nil?
95
+
96
+ affected_items.each do |affected_item|
97
+ add_affected_items(items_out, affected_item)
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -64,6 +64,7 @@ module LaunchDarkly
64
64
  begin
65
65
  res_time = Time.httpdate(response.headers["date"])
66
66
  rescue ArgumentError
67
+ # Ignored
67
68
  end
68
69
  end
69
70
  return EventSenderResult.new(true, false, res_time)
@@ -0,0 +1,58 @@
1
+ require "concurrent"
2
+ require "ldclient-rb/interfaces"
3
+ require "forwardable"
4
+
5
+ module LaunchDarkly
6
+ module Impl
7
+ class FlagTracker
8
+ include LaunchDarkly::Interfaces::FlagTracker
9
+
10
+ extend Forwardable
11
+ def_delegators :@broadcaster, :add_listener, :remove_listener
12
+
13
+ def initialize(broadcaster, eval_fn)
14
+ @broadcaster = broadcaster
15
+ @eval_fn = eval_fn
16
+ end
17
+
18
+ def add_flag_value_change_listener(key, context, listener)
19
+ flag_change_listener = FlagValueChangeAdapter.new(key, context, listener, @eval_fn)
20
+ add_listener(flag_change_listener)
21
+
22
+ flag_change_listener
23
+ end
24
+
25
+ #
26
+ # An adapter which turns a normal flag change listener into a flag value change listener.
27
+ #
28
+ class FlagValueChangeAdapter
29
+ # @param [Symbol] flag_key
30
+ # @param [LaunchDarkly::LDContext] context
31
+ # @param [#update] listener
32
+ # @param [#call] eval_fn
33
+ def initialize(flag_key, context, listener, eval_fn)
34
+ @flag_key = flag_key
35
+ @context = context
36
+ @listener = listener
37
+ @eval_fn = eval_fn
38
+ @value = Concurrent::AtomicReference.new(@eval_fn.call(@flag_key, @context))
39
+ end
40
+
41
+ #
42
+ # @param [LaunchDarkly::Interfaces::FlagChange] flag_change
43
+ #
44
+ def update(flag_change)
45
+ return unless flag_change.key == @flag_key
46
+
47
+ new_eval = @eval_fn.call(@flag_key, @context)
48
+ old_eval = @value.get_and_set(new_eval)
49
+
50
+ return if new_eval == old_eval
51
+
52
+ @listener.update(
53
+ LaunchDarkly::Interfaces::FlagValueChange.new(@flag_key, old_eval, new_eval))
54
+ end
55
+ end
56
+ end
57
+ end
58
+ 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
@@ -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)
@@ -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
@@ -80,10 +88,15 @@ module LaunchDarkly
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