launchdarkly-server-sdk 7.0.4 → 7.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|