launchdarkly-server-sdk 7.0.4 → 7.2.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 +4 -4
- data/lib/ldclient-rb/config.rb +32 -0
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- 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/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 +15 -3
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +16 -0
- data/lib/ldclient-rb/impl/repeating_task.rb +2 -3
- 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/util/store_wrapper.rb +11 -0
- data/lib/ldclient-rb/interfaces.rb +489 -0
- data/lib/ldclient-rb/ldclient.rb +65 -3
- data/lib/ldclient-rb/polling.rb +51 -5
- data/lib/ldclient-rb/requestor.rb +3 -5
- data/lib/ldclient-rb/stream.rb +91 -29
- data/lib/ldclient-rb/util.rb +26 -0
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b14010cc3e61f33c32a83dc937046b23bdd00eabe728cef7a139e4300810cf0
|
4
|
+
data.tar.gz: 21eaf262fe3279bd8d4db2169737d3d934d0471e308b97037473e8d84d81af51
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b59b63876011041a2f37b2bcfabd179318622b1daadb7a33c5eaa217efb68abc2d2cc3d224c7ff6ad1e7a6b8223b15844a318cee767612d155b5b29a6cc135e8
|
7
|
+
data.tar.gz: 6808eb4a6eca0d7680152022344a8aee930b864282a37e12da806b8d311fb9d6cbaac86fef2732e3abbaa75f025c3e67edfab7869d2b117e9a82b14f32f98b39
|
data/lib/ldclient-rb/config.rb
CHANGED
@@ -57,6 +57,7 @@ module LaunchDarkly
|
|
57
57
|
# @option opts [#open] :socket_factory See {#socket_factory}.
|
58
58
|
# @option opts [BigSegmentsConfig] :big_segments See {#big_segments}.
|
59
59
|
# @option opts [Hash] :application See {#application}
|
60
|
+
# @option opts [String] :payload_filter_key See {#payload_filter_key}
|
60
61
|
#
|
61
62
|
def initialize(opts = {})
|
62
63
|
@base_uri = (opts[:base_uri] || Config.default_base_uri).chomp("/")
|
@@ -88,8 +89,24 @@ module LaunchDarkly
|
|
88
89
|
@socket_factory = opts[:socket_factory]
|
89
90
|
@big_segments = opts[:big_segments] || BigSegmentsConfig.new(store: nil)
|
90
91
|
@application = LaunchDarkly::Impl::Util.validate_application_info(opts[:application] || {}, @logger)
|
92
|
+
@payload_filter_key = opts[:payload_filter_key]
|
93
|
+
@data_source_update_sink = nil
|
91
94
|
end
|
92
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
|
+
|
93
110
|
#
|
94
111
|
# The base URL for the LaunchDarkly server. This is configurable mainly for testing
|
95
112
|
# purposes; most users should use the default value.
|
@@ -330,6 +347,21 @@ module LaunchDarkly
|
|
330
347
|
#
|
331
348
|
attr_reader :application
|
332
349
|
|
350
|
+
#
|
351
|
+
# LaunchDarkly Server SDKs historically downloaded all flag configuration and segments for a particular environment
|
352
|
+
# during initialization.
|
353
|
+
#
|
354
|
+
# For some customers, this is an unacceptably large amount of data, and has contributed to performance issues within
|
355
|
+
# their products.
|
356
|
+
#
|
357
|
+
# Filtered environments aim to solve this problem. By allowing customers to specify subsets of an environment's
|
358
|
+
# flags using a filter key, SDKs will initialize faster and use less memory.
|
359
|
+
#
|
360
|
+
# This payload filter key only applies to the default streaming and polling data sources. It will not affect TestData or FileData
|
361
|
+
# data sources, nor will it be applied to any data source provided through the {#data_source} config property.
|
362
|
+
#
|
363
|
+
attr_reader :payload_filter_key
|
364
|
+
|
333
365
|
#
|
334
366
|
# Set to true to opt out of sending diagnostics data.
|
335
367
|
#
|
@@ -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
|
@@ -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
|
@@ -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)
|
@@ -20,8 +20,15 @@ module LaunchDarkly
|
|
20
20
|
rescue LoadError
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
#
|
24
|
+
# @param data_store [LaunchDarkly::Interfaces::FeatureStore]
|
25
|
+
# @param data_source_update_sink [LaunchDarkly::Interfaces::DataSource::UpdateSink, nil] Might be nil for backwards compatibility reasons.
|
26
|
+
# @param logger [Logger]
|
27
|
+
# @param options [Hash]
|
28
|
+
#
|
29
|
+
def initialize(data_store, data_source_update_sink, logger, options={})
|
30
|
+
@data_store = data_source_update_sink || data_store
|
31
|
+
@data_source_update_sink = data_source_update_sink
|
25
32
|
@logger = logger
|
26
33
|
@paths = options[:paths] || []
|
27
34
|
if @paths.is_a? String
|
@@ -80,10 +87,15 @@ module LaunchDarkly
|
|
80
87
|
load_file(path, all_data)
|
81
88
|
rescue => exn
|
82
89
|
LaunchDarkly::Util.log_exception(@logger, "Unable to load flag data from \"#{path}\"", exn)
|
90
|
+
@data_source_update_sink&.update_status(
|
91
|
+
LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
92
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(LaunchDarkly::Interfaces::DataSource::ErrorInfo::INVALID_DATA, 0, exn.to_s, Time.now)
|
93
|
+
)
|
83
94
|
return
|
84
95
|
end
|
85
96
|
end
|
86
|
-
@
|
97
|
+
@data_store.init(all_data)
|
98
|
+
@data_source_update_sink&.update_status(LaunchDarkly::Interfaces::DataSource::Status::VALID, nil)
|
87
99
|
@initialized.make_true
|
88
100
|
end
|
89
101
|
|
@@ -42,6 +42,14 @@ module LaunchDarkly
|
|
42
42
|
@wrapper = LaunchDarkly::Integrations::Util::CachingStoreWrapper.new(core, opts)
|
43
43
|
end
|
44
44
|
|
45
|
+
def monitoring_enabled?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def available?
|
50
|
+
@wrapper.available?
|
51
|
+
end
|
52
|
+
|
45
53
|
#
|
46
54
|
# Default value for the `redis_url` constructor parameter; points to an instance of Redis
|
47
55
|
# running at `localhost` with its default port.
|
@@ -154,6 +162,14 @@ module LaunchDarkly
|
|
154
162
|
@test_hook = opts[:test_hook] # used for unit tests, deliberately undocumented
|
155
163
|
end
|
156
164
|
|
165
|
+
def available?
|
166
|
+
# We don't care what the status is, only that we can connect
|
167
|
+
initialized_internal?
|
168
|
+
true
|
169
|
+
rescue
|
170
|
+
false
|
171
|
+
end
|
172
|
+
|
157
173
|
def description
|
158
174
|
"RedisFeatureStore"
|
159
175
|
end
|