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 +4 -4
- data/README.md +2 -2
- data/lib/ldclient-rb/config.rb +16 -1
- data/lib/ldclient-rb/context.rb +4 -0
- data/lib/ldclient-rb/impl/broadcaster.rb +78 -0
- data/lib/ldclient-rb/impl/context.rb +3 -3
- 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/event_sender.rb +1 -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 +16 -3
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +18 -1
- 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/test_data.rb +3 -3
- 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/stream.rb +88 -28
- 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: 9c039ad353413b88544bf8da89f1400af464323ce17c5677d3df96429fd1966c
|
4
|
+
data.tar.gz: 25eb62e52f75379f22a46df2273df1b7e6658b835f8f0470d03412ce55612467
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
[](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
|
-
|
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
|
|
data/lib/ldclient-rb/config.rb
CHANGED
@@ -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]
|
489
|
+
# @return [Float] 2
|
475
490
|
#
|
476
491
|
def self.default_connect_timeout
|
477
492
|
2
|
data/lib/ldclient-rb/context.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
@@ -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
|
-
|
24
|
-
|
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
|
-
@
|
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
|
|