launchdarkly-server-sdk 8.11.2 → 8.11.3
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 +66 -3
- data/lib/ldclient-rb/context.rb +1 -1
- data/lib/ldclient-rb/data_system.rb +243 -0
- data/lib/ldclient-rb/events.rb +34 -19
- data/lib/ldclient-rb/flags_state.rb +1 -1
- data/lib/ldclient-rb/impl/big_segments.rb +4 -4
- data/lib/ldclient-rb/impl/cache_store.rb +44 -0
- data/lib/ldclient-rb/impl/data_source/polling.rb +108 -0
- data/lib/ldclient-rb/impl/data_source/requestor.rb +106 -0
- data/lib/ldclient-rb/impl/data_source/status_provider.rb +78 -0
- data/lib/ldclient-rb/impl/data_source/stream.rb +198 -0
- data/lib/ldclient-rb/impl/data_source.rb +3 -3
- data/lib/ldclient-rb/impl/data_store/data_kind.rb +108 -0
- data/lib/ldclient-rb/impl/data_store/feature_store_client_wrapper.rb +187 -0
- data/lib/ldclient-rb/impl/data_store/in_memory_feature_store.rb +130 -0
- data/lib/ldclient-rb/impl/data_store/status_provider.rb +82 -0
- data/lib/ldclient-rb/impl/data_store/store.rb +371 -0
- data/lib/ldclient-rb/impl/data_store.rb +11 -97
- data/lib/ldclient-rb/impl/data_system/fdv1.rb +20 -7
- data/lib/ldclient-rb/impl/data_system/fdv2.rb +471 -0
- data/lib/ldclient-rb/impl/data_system/polling.rb +601 -0
- data/lib/ldclient-rb/impl/data_system/protocolv2.rb +264 -0
- data/lib/ldclient-rb/impl/dependency_tracker.rb +21 -9
- data/lib/ldclient-rb/impl/evaluator.rb +3 -2
- data/lib/ldclient-rb/impl/event_sender.rb +4 -3
- data/lib/ldclient-rb/impl/expiring_cache.rb +79 -0
- data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +288 -0
- data/lib/ldclient-rb/impl/memoized_value.rb +34 -0
- data/lib/ldclient-rb/impl/migrations/migrator.rb +2 -1
- data/lib/ldclient-rb/impl/migrations/tracker.rb +2 -1
- data/lib/ldclient-rb/impl/model/serialization.rb +6 -6
- data/lib/ldclient-rb/impl/non_blocking_thread_pool.rb +48 -0
- data/lib/ldclient-rb/impl/repeating_task.rb +2 -2
- data/lib/ldclient-rb/impl/simple_lru_cache.rb +27 -0
- data/lib/ldclient-rb/impl/util.rb +65 -0
- data/lib/ldclient-rb/impl.rb +1 -2
- data/lib/ldclient-rb/in_memory_store.rb +1 -18
- data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +9 -9
- data/lib/ldclient-rb/integrations/test_data.rb +11 -11
- data/lib/ldclient-rb/integrations/test_data_v2/flag_builder_v2.rb +582 -0
- data/lib/ldclient-rb/integrations/test_data_v2.rb +248 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +3 -2
- data/lib/ldclient-rb/interfaces/data_system.rb +755 -0
- data/lib/ldclient-rb/interfaces/feature_store.rb +3 -0
- data/lib/ldclient-rb/ldclient.rb +55 -131
- data/lib/ldclient-rb/util.rb +11 -70
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +8 -17
- metadata +35 -17
- data/lib/ldclient-rb/cache_store.rb +0 -45
- data/lib/ldclient-rb/expiring_cache.rb +0 -77
- data/lib/ldclient-rb/memoized_value.rb +0 -32
- data/lib/ldclient-rb/non_blocking_thread_pool.rb +0 -46
- data/lib/ldclient-rb/polling.rb +0 -102
- data/lib/ldclient-rb/requestor.rb +0 -102
- data/lib/ldclient-rb/simple_lru_cache.rb +0 -25
- data/lib/ldclient-rb/stream.rb +0 -197
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
require "set"
|
|
5
|
+
require "ldclient-rb/impl/data_store"
|
|
6
|
+
require "ldclient-rb/impl/data_store/in_memory_feature_store"
|
|
7
|
+
require "ldclient-rb/impl/dependency_tracker"
|
|
8
|
+
require "ldclient-rb/interfaces/data_system"
|
|
9
|
+
|
|
10
|
+
module LaunchDarkly
|
|
11
|
+
module Impl
|
|
12
|
+
module DataStore
|
|
13
|
+
#
|
|
14
|
+
# Store is a dual-mode persistent/in-memory store that serves requests for
|
|
15
|
+
# data from the evaluation algorithm.
|
|
16
|
+
#
|
|
17
|
+
# At any given moment one of two stores is active: in-memory, or persistent.
|
|
18
|
+
# Once the in-memory store has data (either from initializers or a
|
|
19
|
+
# synchronizer), the persistent store is no longer read from. From that point
|
|
20
|
+
# forward, calls to get data will serve from the memory store.
|
|
21
|
+
#
|
|
22
|
+
class Store
|
|
23
|
+
include LaunchDarkly::Interfaces::DataSystem::SelectorStore
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# Initialize a new Store.
|
|
27
|
+
#
|
|
28
|
+
# @param flag_change_broadcaster [LaunchDarkly::Impl::Broadcaster] Broadcaster for flag change events
|
|
29
|
+
# @param change_set_broadcaster [LaunchDarkly::Impl::Broadcaster] Broadcaster for changeset events
|
|
30
|
+
# @param logger [Logger] The logger instance
|
|
31
|
+
#
|
|
32
|
+
def initialize(flag_change_broadcaster, change_set_broadcaster, logger)
|
|
33
|
+
@logger = logger
|
|
34
|
+
@persistent_store = nil
|
|
35
|
+
@persistent_store_status_provider = nil
|
|
36
|
+
@persistent_store_writable = false
|
|
37
|
+
|
|
38
|
+
# Source of truth for flag evaluations once initialized
|
|
39
|
+
@memory_store = InMemoryFeatureStoreV2.new(logger)
|
|
40
|
+
|
|
41
|
+
# Used to track dependencies between items in the store
|
|
42
|
+
@dependency_tracker = LaunchDarkly::Impl::DependencyTracker.new(logger)
|
|
43
|
+
|
|
44
|
+
# Broadcasters for events
|
|
45
|
+
@flag_change_broadcaster = flag_change_broadcaster
|
|
46
|
+
@change_set_broadcaster = change_set_broadcaster
|
|
47
|
+
|
|
48
|
+
# True if the data in the memory store may be persisted to the persistent store
|
|
49
|
+
@persist = false
|
|
50
|
+
|
|
51
|
+
# Points to the active store. Swapped upon initialization.
|
|
52
|
+
@active_store = @memory_store
|
|
53
|
+
|
|
54
|
+
# Identifies the current data
|
|
55
|
+
@selector = LaunchDarkly::Interfaces::DataSystem::Selector.no_selector
|
|
56
|
+
|
|
57
|
+
# Thread synchronization
|
|
58
|
+
@lock = Mutex.new
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#
|
|
62
|
+
# Configure the store with a persistent store for read-only or read-write access.
|
|
63
|
+
#
|
|
64
|
+
# @param persistent_store [LaunchDarkly::Interfaces::FeatureStore] The persistent store implementation
|
|
65
|
+
# @param writable [Boolean] Whether the persistent store should be written to
|
|
66
|
+
# @param status_provider [LaunchDarkly::Impl::DataStore::StatusProviderV2, nil] Optional status provider for the persistent store
|
|
67
|
+
# @return [Store] self for method chaining
|
|
68
|
+
#
|
|
69
|
+
def with_persistence(persistent_store, writable, status_provider = nil)
|
|
70
|
+
@lock.synchronize do
|
|
71
|
+
@persistent_store = persistent_store
|
|
72
|
+
@persistent_store_writable = writable
|
|
73
|
+
@persistent_store_status_provider = status_provider
|
|
74
|
+
|
|
75
|
+
# Initially use persistent store as active until memory store has data
|
|
76
|
+
@active_store = persistent_store
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
self
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# (see LaunchDarkly::Interfaces::DataSystem::SelectorStore#selector)
|
|
83
|
+
def selector
|
|
84
|
+
@lock.synchronize do
|
|
85
|
+
@selector
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
#
|
|
90
|
+
# Close the store and any persistent store if configured.
|
|
91
|
+
#
|
|
92
|
+
# @return [Exception, nil] Exception if close failed, nil otherwise
|
|
93
|
+
#
|
|
94
|
+
def close
|
|
95
|
+
@lock.synchronize do
|
|
96
|
+
return nil if @persistent_store.nil?
|
|
97
|
+
|
|
98
|
+
begin
|
|
99
|
+
@persistent_store.stop if @persistent_store.respond_to?(:stop)
|
|
100
|
+
rescue => e
|
|
101
|
+
return e
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
nil
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
#
|
|
109
|
+
# Apply a changeset to the store.
|
|
110
|
+
#
|
|
111
|
+
# @param change_set [LaunchDarkly::Interfaces::DataSystem::ChangeSet] The changeset to apply
|
|
112
|
+
# @param persist [Boolean] Whether the changes should be persisted to the persistent store
|
|
113
|
+
# @return [void]
|
|
114
|
+
#
|
|
115
|
+
def apply(change_set, persist)
|
|
116
|
+
collections = changes_to_store_data(change_set.changes)
|
|
117
|
+
|
|
118
|
+
@lock.synchronize do
|
|
119
|
+
begin
|
|
120
|
+
case change_set.intent_code
|
|
121
|
+
when LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_FULL
|
|
122
|
+
set_basis(collections, change_set.selector, persist)
|
|
123
|
+
when LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_CHANGES
|
|
124
|
+
apply_delta(collections, change_set.selector, persist)
|
|
125
|
+
when LaunchDarkly::Interfaces::DataSystem::IntentCode::TRANSFER_NONE
|
|
126
|
+
# No-op, no changes to apply
|
|
127
|
+
return
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Notify changeset listeners
|
|
131
|
+
@change_set_broadcaster.broadcast(change_set)
|
|
132
|
+
rescue => e
|
|
133
|
+
@logger.error { "[LDClient] Couldn't apply changeset: #{e.message}" }
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
#
|
|
139
|
+
# Commit persists the data in the memory store to the persistent store, if configured.
|
|
140
|
+
#
|
|
141
|
+
# @return [Exception, nil] Exception if commit failed, nil otherwise
|
|
142
|
+
#
|
|
143
|
+
def commit
|
|
144
|
+
@lock.synchronize do
|
|
145
|
+
return nil unless should_persist?
|
|
146
|
+
|
|
147
|
+
begin
|
|
148
|
+
# Get all data from memory store and write to persistent store
|
|
149
|
+
all_data = {}
|
|
150
|
+
[FEATURES, SEGMENTS].each do |kind|
|
|
151
|
+
all_data[kind] = @memory_store.all(kind)
|
|
152
|
+
end
|
|
153
|
+
@persistent_store.init(all_data)
|
|
154
|
+
rescue => e
|
|
155
|
+
return e
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
nil
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
#
|
|
163
|
+
# Get the currently active store for reading data.
|
|
164
|
+
#
|
|
165
|
+
# @return [LaunchDarkly::Interfaces::FeatureStore] The active store (memory or persistent)
|
|
166
|
+
#
|
|
167
|
+
def get_active_store
|
|
168
|
+
@lock.synchronize do
|
|
169
|
+
@active_store
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
# Check if the active store is initialized.
|
|
175
|
+
#
|
|
176
|
+
# @return [Boolean]
|
|
177
|
+
#
|
|
178
|
+
def initialized?
|
|
179
|
+
get_active_store.initialized?
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
#
|
|
183
|
+
# Get the data store status provider for the persistent store, if configured.
|
|
184
|
+
#
|
|
185
|
+
# @return [LaunchDarkly::Impl::DataStore::StatusProviderV2, nil] The data store status provider for the persistent store, if configured
|
|
186
|
+
#
|
|
187
|
+
def get_data_store_status_provider
|
|
188
|
+
@lock.synchronize do
|
|
189
|
+
@persistent_store_status_provider
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#
|
|
194
|
+
# Set the basis of the store. Any existing data is discarded.
|
|
195
|
+
#
|
|
196
|
+
# @param collections [Hash{Object => Hash{String => Hash}}] Hash of data kinds to collections of items
|
|
197
|
+
# @param selector [LaunchDarkly::Interfaces::DataSystem::Selector, nil] The selector
|
|
198
|
+
# @param persist [Boolean] Whether to persist the data
|
|
199
|
+
# @return [void]
|
|
200
|
+
#
|
|
201
|
+
private def set_basis(collections, selector, persist)
|
|
202
|
+
# Take snapshot for change detection if we have flag listeners
|
|
203
|
+
old_data = nil
|
|
204
|
+
if @flag_change_broadcaster.has_listeners?
|
|
205
|
+
old_data = {}
|
|
206
|
+
[FEATURES, SEGMENTS].each do |kind|
|
|
207
|
+
old_data[kind] = @memory_store.all(kind)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
ok = @memory_store.set_basis(collections)
|
|
212
|
+
return unless ok
|
|
213
|
+
|
|
214
|
+
# Update dependency tracker
|
|
215
|
+
reset_dependency_tracker(collections)
|
|
216
|
+
|
|
217
|
+
# Update state
|
|
218
|
+
@persist = persist
|
|
219
|
+
@selector = selector || LaunchDarkly::Interfaces::DataSystem::Selector.no_selector
|
|
220
|
+
|
|
221
|
+
# Switch to memory store as active
|
|
222
|
+
@active_store = @memory_store
|
|
223
|
+
|
|
224
|
+
# Persist to persistent store if configured and writable
|
|
225
|
+
@persistent_store.init(collections) if should_persist?
|
|
226
|
+
|
|
227
|
+
# Send change events if we had listeners
|
|
228
|
+
if old_data
|
|
229
|
+
affected_items = compute_changed_items_for_full_data_set(old_data, collections)
|
|
230
|
+
send_change_events(affected_items)
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
#
|
|
235
|
+
# Apply a delta update to the store.
|
|
236
|
+
#
|
|
237
|
+
# @param collections [Hash{Object => Hash{String => Hash}}] Hash of data kinds to collections with updates
|
|
238
|
+
# @param selector [LaunchDarkly::Interfaces::DataSystem::Selector, nil] The selector
|
|
239
|
+
# @param persist [Boolean] Whether to persist the changes
|
|
240
|
+
# @return [void]
|
|
241
|
+
#
|
|
242
|
+
private def apply_delta(collections, selector, persist)
|
|
243
|
+
ok = @memory_store.apply_delta(collections)
|
|
244
|
+
return unless ok
|
|
245
|
+
|
|
246
|
+
has_listeners = @flag_change_broadcaster.has_listeners?
|
|
247
|
+
affected_items = Set.new
|
|
248
|
+
|
|
249
|
+
collections.each do |kind, collection|
|
|
250
|
+
collection.each do |key, item|
|
|
251
|
+
@dependency_tracker.update_dependencies_from(kind, key, item)
|
|
252
|
+
if has_listeners
|
|
253
|
+
@dependency_tracker.add_affected_items(affected_items, { kind: kind, key: key })
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Update state
|
|
259
|
+
@persist = persist
|
|
260
|
+
@selector = selector || LaunchDarkly::Interfaces::DataSystem::Selector.no_selector
|
|
261
|
+
|
|
262
|
+
if should_persist?
|
|
263
|
+
collections.each do |kind, kind_data|
|
|
264
|
+
kind_data.each do |_key, item|
|
|
265
|
+
@persistent_store.upsert(kind, item)
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Send change events
|
|
271
|
+
send_change_events(affected_items) unless affected_items.empty?
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
#
|
|
275
|
+
# Returns whether data should be persisted to the persistent store.
|
|
276
|
+
#
|
|
277
|
+
# @return [Boolean]
|
|
278
|
+
#
|
|
279
|
+
private def should_persist?
|
|
280
|
+
@persist && !@persistent_store.nil? && @persistent_store_writable
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
#
|
|
284
|
+
# Convert a list of Changes to the pre-existing format used by FeatureStore.
|
|
285
|
+
#
|
|
286
|
+
# @param changes [Array<LaunchDarkly::Interfaces::DataSystem::Change>] List of changes
|
|
287
|
+
# @return [Hash{DataKind => Hash{String => Hash}}] Hash suitable for FeatureStore operations
|
|
288
|
+
#
|
|
289
|
+
private def changes_to_store_data(changes)
|
|
290
|
+
all_data = {
|
|
291
|
+
FEATURES => {},
|
|
292
|
+
SEGMENTS => {},
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
changes.each do |change|
|
|
296
|
+
kind = change.kind == LaunchDarkly::Interfaces::DataSystem::ObjectKind::FLAG ? FEATURES : SEGMENTS
|
|
297
|
+
if change.action == LaunchDarkly::Interfaces::DataSystem::ChangeType::PUT && !change.object.nil?
|
|
298
|
+
all_data[kind][change.key] = change.object
|
|
299
|
+
elsif change.action == LaunchDarkly::Interfaces::DataSystem::ChangeType::DELETE
|
|
300
|
+
all_data[kind][change.key] = { key: change.key, deleted: true, version: change.version }
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
all_data
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
#
|
|
308
|
+
# Reset dependency tracker with new full data set.
|
|
309
|
+
#
|
|
310
|
+
# @param all_data [Hash{DataKind => Hash{String => Hash}}] Hash of data kinds to items
|
|
311
|
+
# @return [void]
|
|
312
|
+
#
|
|
313
|
+
private def reset_dependency_tracker(all_data)
|
|
314
|
+
@dependency_tracker.reset
|
|
315
|
+
all_data.each do |kind, items|
|
|
316
|
+
items.each do |key, item|
|
|
317
|
+
@dependency_tracker.update_dependencies_from(kind, key, item)
|
|
318
|
+
end
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
#
|
|
323
|
+
# Send flag change events for affected items.
|
|
324
|
+
#
|
|
325
|
+
# @param affected_items [Set<Hash>] Set of {kind:, key:} hashes
|
|
326
|
+
# @return [void]
|
|
327
|
+
#
|
|
328
|
+
private def send_change_events(affected_items)
|
|
329
|
+
affected_items.each do |item|
|
|
330
|
+
if item[:kind] == FEATURES
|
|
331
|
+
@flag_change_broadcaster.broadcast(LaunchDarkly::Interfaces::FlagChange.new(item[:key]))
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
#
|
|
337
|
+
# Compute which items changed between old and new data sets.
|
|
338
|
+
#
|
|
339
|
+
# @param old_data [Hash{DataKind => Hash{String => Hash}}] Old data hash
|
|
340
|
+
# @param new_data [Hash{DataKind => Hash{String => Hash}}] New data hash
|
|
341
|
+
# @return [Set<Hash>] Set of {kind:, key:} hashes
|
|
342
|
+
#
|
|
343
|
+
private def compute_changed_items_for_full_data_set(old_data, new_data)
|
|
344
|
+
affected_items = Set.new
|
|
345
|
+
|
|
346
|
+
[FEATURES, SEGMENTS].each do |kind|
|
|
347
|
+
old_items = old_data[kind] || {}
|
|
348
|
+
new_items = new_data[kind] || {}
|
|
349
|
+
|
|
350
|
+
# Get all keys from both old and new data
|
|
351
|
+
all_keys = Set.new(old_items.keys) | Set.new(new_items.keys)
|
|
352
|
+
|
|
353
|
+
all_keys.each do |key|
|
|
354
|
+
old_item = old_items[key]
|
|
355
|
+
new_item = new_items[key]
|
|
356
|
+
|
|
357
|
+
# If either is missing or versions differ, it's a change
|
|
358
|
+
if old_item.nil? || new_item.nil?
|
|
359
|
+
@dependency_tracker.add_affected_items(affected_items, { kind: kind, key: key })
|
|
360
|
+
elsif old_item[:version] != new_item[:version]
|
|
361
|
+
@dependency_tracker.add_affected_items(affected_items, { kind: kind, key: key })
|
|
362
|
+
end
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
affected_items
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
@@ -1,109 +1,23 @@
|
|
|
1
1
|
require 'concurrent'
|
|
2
2
|
require "ldclient-rb/interfaces"
|
|
3
|
+
require "ldclient-rb/impl/data_store/data_kind"
|
|
3
4
|
|
|
4
5
|
module LaunchDarkly
|
|
5
6
|
module Impl
|
|
6
7
|
module DataStore
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
FEATURE_PREREQ_FN = lambda { |flag| (flag[:prerequisites] || []).map { |p| p[:key] } }.freeze
|
|
13
|
-
|
|
14
|
-
attr_reader :namespace
|
|
15
|
-
attr_reader :priority
|
|
16
|
-
|
|
17
|
-
#
|
|
18
|
-
# @param namespace [String]
|
|
19
|
-
# @param priority [Integer]
|
|
20
|
-
#
|
|
21
|
-
def initialize(namespace:, priority:)
|
|
22
|
-
@namespace = namespace
|
|
23
|
-
@priority = priority
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
# Maintain the same behavior when these data kinds were standard ruby hashes.
|
|
28
|
-
#
|
|
29
|
-
# @param key [Symbol]
|
|
30
|
-
# @return [Object]
|
|
31
|
-
#
|
|
32
|
-
def [](key)
|
|
33
|
-
return priority if key == :priority
|
|
34
|
-
return namespace if key == :namespace
|
|
35
|
-
return get_dependency_keys_fn() if key == :get_dependency_keys
|
|
36
|
-
nil
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
#
|
|
40
|
-
# Retrieve the dependency keys for a particular data kind. Right now, this is only defined for flags.
|
|
8
|
+
# These constants denote the types of data that can be stored in the feature store. If
|
|
9
|
+
# we add another storable data type in the future, as long as it follows the same pattern
|
|
10
|
+
# (having "key", "version", and "deleted" properties), we only need to add a corresponding
|
|
11
|
+
# constant here and the existing store should be able to handle it.
|
|
41
12
|
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
FEATURE_PREREQ_FN
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def eql?(other)
|
|
49
|
-
other.is_a?(DataKind) && namespace == other.namespace && priority == other.priority
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def hash
|
|
53
|
-
[namespace, priority].hash
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
class StatusProvider
|
|
58
|
-
include LaunchDarkly::Interfaces::DataStore::StatusProvider
|
|
59
|
-
|
|
60
|
-
def initialize(store, update_sink)
|
|
61
|
-
# @type [LaunchDarkly::Impl::FeatureStoreClientWrapper]
|
|
62
|
-
@store = store
|
|
63
|
-
# @type [UpdateSink]
|
|
64
|
-
@update_sink = update_sink
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def status
|
|
68
|
-
@update_sink.last_status.get
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def monitoring_enabled?
|
|
72
|
-
@store.monitoring_enabled?
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def add_listener(listener)
|
|
76
|
-
@update_sink.broadcaster.add_listener(listener)
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
def remove_listener(listener)
|
|
80
|
-
@update_sink.broadcaster.remove_listener(listener)
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
class UpdateSink
|
|
85
|
-
include LaunchDarkly::Interfaces::DataStore::UpdateSink
|
|
86
|
-
|
|
87
|
-
# @return [LaunchDarkly::Impl::Broadcaster]
|
|
88
|
-
attr_reader :broadcaster
|
|
89
|
-
|
|
90
|
-
# @return [Concurrent::AtomicReference]
|
|
91
|
-
attr_reader :last_status
|
|
13
|
+
# The :priority and :get_dependency_keys properties are used by FeatureStoreDataSetSorter
|
|
14
|
+
# to ensure data consistency during non-atomic updates.
|
|
92
15
|
|
|
93
|
-
|
|
94
|
-
@broadcaster = broadcaster
|
|
95
|
-
@last_status = Concurrent::AtomicReference.new(
|
|
96
|
-
LaunchDarkly::Interfaces::DataStore::Status.new(true, false)
|
|
97
|
-
)
|
|
98
|
-
end
|
|
16
|
+
FEATURES = DataKind.new(namespace: "features", priority: 1).freeze
|
|
99
17
|
|
|
100
|
-
|
|
101
|
-
return if status.nil?
|
|
18
|
+
SEGMENTS = DataKind.new(namespace: "segments", priority: 0).freeze
|
|
102
19
|
|
|
103
|
-
|
|
104
|
-
@broadcaster.broadcast(status) unless old_status == status
|
|
105
|
-
end
|
|
106
|
-
end
|
|
20
|
+
ALL_KINDS = [FEATURES, SEGMENTS].freeze
|
|
107
21
|
end
|
|
108
22
|
end
|
|
109
|
-
end
|
|
23
|
+
end
|
|
@@ -2,6 +2,9 @@ require 'concurrent'
|
|
|
2
2
|
require 'ldclient-rb/impl/broadcaster'
|
|
3
3
|
require 'ldclient-rb/impl/data_source'
|
|
4
4
|
require 'ldclient-rb/impl/data_source/null_processor'
|
|
5
|
+
require 'ldclient-rb/impl/data_source/polling'
|
|
6
|
+
require 'ldclient-rb/impl/data_source/requestor'
|
|
7
|
+
require 'ldclient-rb/impl/data_source/stream'
|
|
5
8
|
require 'ldclient-rb/impl/data_store'
|
|
6
9
|
require 'ldclient-rb/impl/data_system'
|
|
7
10
|
require 'ldclient-rb/impl/store_client_wrapper'
|
|
@@ -35,13 +38,22 @@ module LaunchDarkly
|
|
|
35
38
|
@data_store_broadcaster
|
|
36
39
|
)
|
|
37
40
|
|
|
38
|
-
#
|
|
41
|
+
# Preserve the original unwrapped store to avoid nested wrappers on postfork
|
|
42
|
+
original_store = @config.feature_store
|
|
43
|
+
if original_store.is_a?(LaunchDarkly::Impl::FeatureStoreClientWrapper)
|
|
44
|
+
original_store = original_store.instance_variable_get(:@store)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Wrap the original data store with client wrapper (must be created before status provider)
|
|
39
48
|
@store_wrapper = LaunchDarkly::Impl::FeatureStoreClientWrapper.new(
|
|
40
|
-
|
|
49
|
+
original_store,
|
|
41
50
|
@data_store_update_sink,
|
|
42
51
|
@config.logger
|
|
43
52
|
)
|
|
44
53
|
|
|
54
|
+
# Update config to use wrapped store so data sources can access it
|
|
55
|
+
@config.instance_variable_set(:@feature_store, @store_wrapper)
|
|
56
|
+
|
|
45
57
|
# Create status provider with store wrapper
|
|
46
58
|
@data_store_status_provider = LaunchDarkly::Impl::DataStore::StatusProvider.new(
|
|
47
59
|
@store_wrapper,
|
|
@@ -80,6 +92,7 @@ module LaunchDarkly
|
|
|
80
92
|
# (see DataSystem#stop)
|
|
81
93
|
def stop
|
|
82
94
|
@update_processor&.stop
|
|
95
|
+
@store_wrapper.stop
|
|
83
96
|
@shared_executor.shutdown
|
|
84
97
|
end
|
|
85
98
|
|
|
@@ -149,14 +162,14 @@ module LaunchDarkly
|
|
|
149
162
|
return LaunchDarkly::Impl::DataSource::NullUpdateProcessor.new if @config.offline? || @config.use_ldd?
|
|
150
163
|
|
|
151
164
|
if @config.stream?
|
|
152
|
-
|
|
153
|
-
return LaunchDarkly::StreamProcessor.new(@sdk_key, @config, @diagnostic_accumulator)
|
|
165
|
+
return LaunchDarkly::Impl::DataSource::StreamProcessor.new(@sdk_key, @config, @diagnostic_accumulator)
|
|
154
166
|
end
|
|
155
167
|
|
|
156
168
|
# Polling processor
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
LaunchDarkly::
|
|
169
|
+
@config.logger.info { "Disabling streaming API" }
|
|
170
|
+
@config.logger.warn { "You should only disable the streaming API if instructed to do so by LaunchDarkly support" }
|
|
171
|
+
requestor = LaunchDarkly::Impl::DataSource::Requestor.new(@sdk_key, @config)
|
|
172
|
+
LaunchDarkly::Impl::DataSource::PollingProcessor.new(@config, requestor)
|
|
160
173
|
end
|
|
161
174
|
end
|
|
162
175
|
end
|