launchdarkly-server-sdk 8.11.2-java → 8.12.0-java
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 +69 -9
- data/lib/ldclient-rb/context.rb +1 -1
- data/lib/ldclient-rb/data_system.rb +227 -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 +113 -0
- data/lib/ldclient-rb/impl/data_source/status_provider.rb +83 -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 +76 -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/data_source_builder_common.rb +77 -0
- data/lib/ldclient-rb/impl/data_system/fdv1.rb +20 -7
- data/lib/ldclient-rb/impl/data_system/fdv2.rb +472 -0
- data/lib/ldclient-rb/impl/data_system/http_config_options.rb +32 -0
- data/lib/ldclient-rb/impl/data_system/polling.rb +628 -0
- data/lib/ldclient-rb/impl/data_system/protocolv2.rb +264 -0
- data/lib/ldclient-rb/impl/data_system/streaming.rb +401 -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 +14 -6
- 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/file_data_source_v2.rb +460 -0
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +290 -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/store_data_set_sorter.rb +1 -1
- data/lib/ldclient-rb/impl/util.rb +71 -0
- data/lib/ldclient-rb/impl.rb +1 -2
- data/lib/ldclient-rb/in_memory_store.rb +1 -18
- data/lib/ldclient-rb/integrations/file_data.rb +67 -0
- 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 +254 -0
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +3 -2
- data/lib/ldclient-rb/interfaces/data_system.rb +704 -0
- data/lib/ldclient-rb/interfaces/feature_store.rb +5 -2
- data/lib/ldclient-rb/ldclient.rb +66 -132
- data/lib/ldclient-rb/util.rb +11 -70
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +9 -17
- metadata +41 -19
- 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,472 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
require "ldclient-rb/config"
|
|
5
|
+
require "ldclient-rb/impl/data_system"
|
|
6
|
+
require "ldclient-rb/impl/data_store/store"
|
|
7
|
+
require "ldclient-rb/impl/data_store/feature_store_client_wrapper"
|
|
8
|
+
require "ldclient-rb/impl/data_source/status_provider"
|
|
9
|
+
require "ldclient-rb/impl/data_store/status_provider"
|
|
10
|
+
require "ldclient-rb/impl/broadcaster"
|
|
11
|
+
require "ldclient-rb/impl/repeating_task"
|
|
12
|
+
require "ldclient-rb/interfaces/data_system"
|
|
13
|
+
|
|
14
|
+
module LaunchDarkly
|
|
15
|
+
module Impl
|
|
16
|
+
module DataSystem
|
|
17
|
+
#
|
|
18
|
+
# Represents the possible outcomes from consuming synchronizer results.
|
|
19
|
+
#
|
|
20
|
+
# Used by {FDv2#consume_synchronizer_results} to indicate what action the
|
|
21
|
+
# synchronizer loop should take next.
|
|
22
|
+
#
|
|
23
|
+
module SyncResult
|
|
24
|
+
# Temporarily move to the next synchronizer in the list.
|
|
25
|
+
# The current synchronizer remains available for future recovery.
|
|
26
|
+
FALLBACK = :fallback
|
|
27
|
+
|
|
28
|
+
# Return to the first synchronizer in the list.
|
|
29
|
+
# Used when recovery conditions are met on a fallback synchronizer.
|
|
30
|
+
RECOVER = :recover
|
|
31
|
+
|
|
32
|
+
# Permanently remove the current synchronizer from the list.
|
|
33
|
+
# Used for unrecoverable failures (OFF state, exceptions).
|
|
34
|
+
REMOVE = :remove
|
|
35
|
+
|
|
36
|
+
# Switch to the FDv1 protocol fallback.
|
|
37
|
+
# Replaces the synchronizer list with the FDv1 fallback synchronizer.
|
|
38
|
+
FDV1 = :fdv1
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# FDv2 is an implementation of the DataSystem interface that uses the Flag Delivery V2 protocol
|
|
42
|
+
# for obtaining and keeping data up-to-date. Additionally, it operates with an optional persistent
|
|
43
|
+
# store in read-only or read/write mode.
|
|
44
|
+
class FDv2
|
|
45
|
+
include LaunchDarkly::Impl::DataSystem
|
|
46
|
+
|
|
47
|
+
# Initialize a new FDv2 data system.
|
|
48
|
+
#
|
|
49
|
+
# @param sdk_key [String] The SDK key
|
|
50
|
+
# @param config [LaunchDarkly::Config] Configuration for initializers and synchronizers
|
|
51
|
+
# @param data_system_config [LaunchDarkly::DataSystemConfig] FDv2 data system configuration
|
|
52
|
+
def initialize(sdk_key, config, data_system_config)
|
|
53
|
+
@sdk_key = sdk_key
|
|
54
|
+
@config = config
|
|
55
|
+
@data_system_config = data_system_config
|
|
56
|
+
@logger = config.logger
|
|
57
|
+
@synchronizer_builders = data_system_config.synchronizers || []
|
|
58
|
+
@fdv1_fallback_synchronizer_builder = data_system_config.fdv1_fallback_synchronizer
|
|
59
|
+
@disabled = @config.offline?
|
|
60
|
+
|
|
61
|
+
# Diagnostic accumulator provided by client for streaming metrics
|
|
62
|
+
@diagnostic_accumulator = nil
|
|
63
|
+
|
|
64
|
+
# Shared executor for all broadcasters
|
|
65
|
+
@shared_executor = Concurrent::SingleThreadExecutor.new
|
|
66
|
+
|
|
67
|
+
# Set up event listeners
|
|
68
|
+
@flag_change_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @logger)
|
|
69
|
+
@change_set_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @logger)
|
|
70
|
+
@data_source_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @logger)
|
|
71
|
+
@data_store_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @logger)
|
|
72
|
+
|
|
73
|
+
recovery_listener = Object.new
|
|
74
|
+
recovery_listener.define_singleton_method(:update) do |data_store_status|
|
|
75
|
+
persistent_store_outage_recovery(data_store_status)
|
|
76
|
+
end
|
|
77
|
+
@data_store_broadcaster.add_listener(recovery_listener)
|
|
78
|
+
|
|
79
|
+
# Create the store
|
|
80
|
+
@store = LaunchDarkly::Impl::DataStore::Store.new(@flag_change_broadcaster, @change_set_broadcaster, @logger)
|
|
81
|
+
|
|
82
|
+
# Status providers
|
|
83
|
+
@data_source_status_provider = LaunchDarkly::Impl::DataSource::StatusProviderV2.new(
|
|
84
|
+
@data_source_broadcaster
|
|
85
|
+
)
|
|
86
|
+
@data_store_status_provider = LaunchDarkly::Impl::DataStore::StatusProviderV2.new(nil, @data_store_broadcaster)
|
|
87
|
+
|
|
88
|
+
# Configure persistent store if provided
|
|
89
|
+
if @data_system_config.data_store
|
|
90
|
+
@data_store_status_provider = LaunchDarkly::Impl::DataStore::StatusProviderV2.new(
|
|
91
|
+
@data_system_config.data_store,
|
|
92
|
+
@data_store_broadcaster
|
|
93
|
+
)
|
|
94
|
+
writable = @data_system_config.data_store_mode == :read_write
|
|
95
|
+
wrapper = LaunchDarkly::Impl::DataStore::FeatureStoreClientWrapperV2.new(
|
|
96
|
+
@data_system_config.data_store,
|
|
97
|
+
@data_store_status_provider,
|
|
98
|
+
@logger
|
|
99
|
+
)
|
|
100
|
+
@store.with_persistence(wrapper, writable, @data_store_status_provider)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Threading
|
|
104
|
+
@stop_event = Concurrent::Event.new
|
|
105
|
+
@ready_event = Concurrent::Event.new
|
|
106
|
+
@lock = Mutex.new
|
|
107
|
+
@active_synchronizer = nil
|
|
108
|
+
@threads = []
|
|
109
|
+
|
|
110
|
+
# Track configuration
|
|
111
|
+
@configured_with_data_sources = (@data_system_config.initializers && !@data_system_config.initializers.empty?) ||
|
|
112
|
+
!@synchronizer_builders.empty?
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# (see DataSystem#start)
|
|
116
|
+
def start
|
|
117
|
+
if @disabled
|
|
118
|
+
@logger.warn { "[LDClient] Data system is disabled, SDK will return application-defined default values" }
|
|
119
|
+
@ready_event.set
|
|
120
|
+
return @ready_event
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
@stop_event.reset
|
|
124
|
+
@ready_event.reset
|
|
125
|
+
|
|
126
|
+
# Start the main coordination thread
|
|
127
|
+
main_thread = Thread.new { run_main_loop }
|
|
128
|
+
main_thread.name = "FDv2-main"
|
|
129
|
+
@threads << main_thread
|
|
130
|
+
|
|
131
|
+
@ready_event
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# (see DataSystem#stop)
|
|
135
|
+
def stop
|
|
136
|
+
@stop_event.set
|
|
137
|
+
|
|
138
|
+
@lock.synchronize do
|
|
139
|
+
if @active_synchronizer
|
|
140
|
+
begin
|
|
141
|
+
@active_synchronizer.stop
|
|
142
|
+
rescue => e
|
|
143
|
+
@logger.error { "[LDClient] Error stopping active data source: #{e.message}" }
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Wait for all threads to complete
|
|
149
|
+
@threads.each do |thread|
|
|
150
|
+
next unless thread.alive?
|
|
151
|
+
|
|
152
|
+
thread.join(5.0) # 5 second timeout
|
|
153
|
+
@logger.warn { "[LDClient] Thread #{thread.name} did not terminate in time" } if thread.alive?
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Close the store
|
|
157
|
+
@store.close
|
|
158
|
+
|
|
159
|
+
# Shutdown the shared executor
|
|
160
|
+
@shared_executor.shutdown
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# (see DataSystem#set_diagnostic_accumulator)
|
|
164
|
+
def set_diagnostic_accumulator(diagnostic_accumulator)
|
|
165
|
+
@diagnostic_accumulator = diagnostic_accumulator
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# (see DataSystem#store)
|
|
169
|
+
def store
|
|
170
|
+
@store.get_active_store
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# (see DataSystem#data_source_status_provider)
|
|
174
|
+
def data_source_status_provider
|
|
175
|
+
@data_source_status_provider
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# (see DataSystem#data_store_status_provider)
|
|
179
|
+
def data_store_status_provider
|
|
180
|
+
@data_store_status_provider
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# (see DataSystem#flag_change_broadcaster)
|
|
184
|
+
def flag_change_broadcaster
|
|
185
|
+
@flag_change_broadcaster
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# (see DataSystem#data_availability)
|
|
189
|
+
def data_availability
|
|
190
|
+
return DataAvailability::REFRESHED if @store.selector.defined?
|
|
191
|
+
return DataAvailability::CACHED if !@configured_with_data_sources || @store.initialized?
|
|
192
|
+
|
|
193
|
+
DataAvailability::DEFAULTS
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# (see DataSystem#target_availability)
|
|
197
|
+
def target_availability
|
|
198
|
+
return DataAvailability::REFRESHED if @configured_with_data_sources
|
|
199
|
+
|
|
200
|
+
DataAvailability::CACHED
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
|
|
205
|
+
#
|
|
206
|
+
# Main coordination loop that manages initializers and synchronizers.
|
|
207
|
+
#
|
|
208
|
+
# @return [void]
|
|
209
|
+
#
|
|
210
|
+
def run_main_loop
|
|
211
|
+
begin
|
|
212
|
+
@data_source_status_provider.update_status(
|
|
213
|
+
LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
|
|
214
|
+
nil
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# Run initializers first
|
|
218
|
+
run_initializers
|
|
219
|
+
|
|
220
|
+
# Run synchronizers
|
|
221
|
+
run_synchronizers
|
|
222
|
+
rescue => e
|
|
223
|
+
@logger.error { "[LDClient] Error in FDv2 main loop: #{e.message}" }
|
|
224
|
+
@ready_event.set
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
#
|
|
229
|
+
# Run initializers to get initial data.
|
|
230
|
+
#
|
|
231
|
+
# @return [void]
|
|
232
|
+
#
|
|
233
|
+
def run_initializers
|
|
234
|
+
return unless @data_system_config.initializers
|
|
235
|
+
|
|
236
|
+
@data_system_config.initializers.each do |initializer_builder|
|
|
237
|
+
return if @stop_event.set?
|
|
238
|
+
|
|
239
|
+
begin
|
|
240
|
+
initializer = initializer_builder.build(@sdk_key, @config)
|
|
241
|
+
@logger.info { "[LDClient] Attempting to initialize via #{initializer.name}" }
|
|
242
|
+
|
|
243
|
+
basis_result = initializer.fetch(@store)
|
|
244
|
+
|
|
245
|
+
if basis_result.success?
|
|
246
|
+
basis = basis_result.value
|
|
247
|
+
@logger.info { "[LDClient] Initialized via #{initializer.name}" }
|
|
248
|
+
|
|
249
|
+
# Apply the basis to the store
|
|
250
|
+
@store.apply(basis.change_set, basis.persist)
|
|
251
|
+
|
|
252
|
+
# Set ready event if and only if a selector is defined for the changeset
|
|
253
|
+
if basis.change_set.selector && basis.change_set.selector.defined?
|
|
254
|
+
@ready_event.set
|
|
255
|
+
return
|
|
256
|
+
end
|
|
257
|
+
else
|
|
258
|
+
@logger.warn { "[LDClient] Initializer #{initializer.name} failed: #{basis_result.error}" }
|
|
259
|
+
end
|
|
260
|
+
rescue => e
|
|
261
|
+
@logger.error { "[LDClient] Initializer failed with exception: #{e.message}" }
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
#
|
|
267
|
+
# Run synchronizers to keep data up-to-date.
|
|
268
|
+
#
|
|
269
|
+
# @return [void]
|
|
270
|
+
#
|
|
271
|
+
def run_synchronizers
|
|
272
|
+
# If no synchronizers configured, just set ready and return
|
|
273
|
+
if @synchronizer_builders.empty?
|
|
274
|
+
@ready_event.set
|
|
275
|
+
return
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# Start synchronizer loop in a separate thread
|
|
279
|
+
sync_thread = Thread.new { synchronizer_loop }
|
|
280
|
+
sync_thread.name = "FDv2-synchronizers"
|
|
281
|
+
@threads << sync_thread
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
#
|
|
285
|
+
# Synchronizer loop that manages synchronizers and fallbacks.
|
|
286
|
+
#
|
|
287
|
+
# @return [void]
|
|
288
|
+
#
|
|
289
|
+
def synchronizer_loop
|
|
290
|
+
# Track the current index in the synchronizer array
|
|
291
|
+
current_index = 0
|
|
292
|
+
|
|
293
|
+
begin
|
|
294
|
+
while !@stop_event.set? && current_index < @synchronizer_builders.length
|
|
295
|
+
synchronizer_builder = @synchronizer_builders[current_index]
|
|
296
|
+
is_primary = current_index == 0
|
|
297
|
+
|
|
298
|
+
begin
|
|
299
|
+
@lock.synchronize do
|
|
300
|
+
sync = synchronizer_builder.build(@sdk_key, @config)
|
|
301
|
+
if sync.respond_to?(:set_diagnostic_accumulator) && @diagnostic_accumulator
|
|
302
|
+
sync.set_diagnostic_accumulator(@diagnostic_accumulator)
|
|
303
|
+
end
|
|
304
|
+
@active_synchronizer = sync
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
@logger.info { "[LDClient] Synchronizer[#{current_index}] #{@active_synchronizer.name} is starting" }
|
|
308
|
+
|
|
309
|
+
sync_result = consume_synchronizer_results(@active_synchronizer, check_recovery: !is_primary)
|
|
310
|
+
|
|
311
|
+
break if @stop_event.set?
|
|
312
|
+
|
|
313
|
+
case sync_result
|
|
314
|
+
when SyncResult::FDV1
|
|
315
|
+
if @fdv1_fallback_synchronizer_builder
|
|
316
|
+
@synchronizer_builders = [@fdv1_fallback_synchronizer_builder]
|
|
317
|
+
current_index = 0
|
|
318
|
+
next
|
|
319
|
+
end
|
|
320
|
+
# No FDv1 fallback configured, treat as regular fallback
|
|
321
|
+
current_index += 1
|
|
322
|
+
when SyncResult::RECOVER
|
|
323
|
+
@logger.info { "[LDClient] Recovery condition met, returning to primary synchronizer" }
|
|
324
|
+
current_index = 0
|
|
325
|
+
when SyncResult::REMOVE
|
|
326
|
+
@logger.info { "[LDClient] Removing synchronizer from list due to permanent failure" }
|
|
327
|
+
@synchronizer_builders.delete_at(current_index)
|
|
328
|
+
else
|
|
329
|
+
@logger.info { "[LDClient] Fallback condition met" }
|
|
330
|
+
current_index += 1
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
current_index = 0 if current_index >= @synchronizer_builders.length
|
|
334
|
+
|
|
335
|
+
if @synchronizer_builders.length == 0
|
|
336
|
+
@logger.warn { "[LDClient] No more synchronizers available" }
|
|
337
|
+
@data_source_status_provider.update_status(
|
|
338
|
+
LaunchDarkly::Interfaces::DataSource::Status::OFF,
|
|
339
|
+
@data_source_status_provider.status.last_error
|
|
340
|
+
)
|
|
341
|
+
break
|
|
342
|
+
end
|
|
343
|
+
rescue => e
|
|
344
|
+
@logger.error { "[LDClient] Failed to build synchronizer: #{e.message}" }
|
|
345
|
+
break
|
|
346
|
+
end
|
|
347
|
+
end
|
|
348
|
+
rescue => e
|
|
349
|
+
@logger.error { "[LDClient] Error in synchronizer loop: #{e.message}" }
|
|
350
|
+
ensure
|
|
351
|
+
# Ensure we always set the ready event when exiting
|
|
352
|
+
@ready_event.set
|
|
353
|
+
@lock.synchronize do
|
|
354
|
+
@active_synchronizer&.stop
|
|
355
|
+
@active_synchronizer = nil
|
|
356
|
+
end
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
#
|
|
361
|
+
# Consume results from a synchronizer until a condition is met or it fails.
|
|
362
|
+
#
|
|
363
|
+
# @param synchronizer [Object] The synchronizer
|
|
364
|
+
# @param check_recovery [Boolean] Whether to check recovery condition (healthy too long)
|
|
365
|
+
# @return [Symbol] One of {SyncResult::FALLBACK}, {SyncResult::RECOVER}, {SyncResult::REMOVE}, or {SyncResult::FDV1}
|
|
366
|
+
#
|
|
367
|
+
def consume_synchronizer_results(synchronizer, check_recovery: false)
|
|
368
|
+
action_queue = Queue.new
|
|
369
|
+
timer = LaunchDarkly::Impl::RepeatingTask.new(10, 10, -> { action_queue.push("check") }, @logger, "FDv2-sync-cond-timer")
|
|
370
|
+
|
|
371
|
+
# Start reader thread
|
|
372
|
+
sync_reader = Thread.new do
|
|
373
|
+
begin
|
|
374
|
+
synchronizer.sync(@store) do |update|
|
|
375
|
+
action_queue.push(update)
|
|
376
|
+
end
|
|
377
|
+
ensure
|
|
378
|
+
action_queue.push("quit")
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
sync_reader.name = "FDv2-sync-reader"
|
|
382
|
+
|
|
383
|
+
begin
|
|
384
|
+
timer.start
|
|
385
|
+
|
|
386
|
+
loop do
|
|
387
|
+
update = action_queue.pop
|
|
388
|
+
|
|
389
|
+
if update.is_a?(String)
|
|
390
|
+
break if update == "quit"
|
|
391
|
+
|
|
392
|
+
if update == "check"
|
|
393
|
+
# Check condition periodically
|
|
394
|
+
current_status = @data_source_status_provider.status
|
|
395
|
+
return SyncResult::RECOVER if check_recovery && recovery_condition(current_status)
|
|
396
|
+
return SyncResult::FALLBACK if fallback_condition(current_status)
|
|
397
|
+
end
|
|
398
|
+
next
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
@logger.info { "[LDClient] Synchronizer #{synchronizer.name} update: #{update.state}" }
|
|
402
|
+
return SyncResult::FALLBACK if @stop_event.set?
|
|
403
|
+
|
|
404
|
+
# Handle the update
|
|
405
|
+
@store.apply(update.change_set, true) if update.change_set
|
|
406
|
+
|
|
407
|
+
# Set ready event on valid update
|
|
408
|
+
@ready_event.set if update.state == LaunchDarkly::Interfaces::DataSource::Status::VALID
|
|
409
|
+
|
|
410
|
+
# Update status
|
|
411
|
+
@data_source_status_provider.update_status(update.state, update.error)
|
|
412
|
+
|
|
413
|
+
return SyncResult::FDV1 if update.revert_to_fdv1
|
|
414
|
+
|
|
415
|
+
return SyncResult::REMOVE if update.state == LaunchDarkly::Interfaces::DataSource::Status::OFF
|
|
416
|
+
end
|
|
417
|
+
rescue => e
|
|
418
|
+
@logger.error { "[LDClient] Error consuming synchronizer results: #{e.message}" }
|
|
419
|
+
return SyncResult::REMOVE
|
|
420
|
+
ensure
|
|
421
|
+
synchronizer.stop
|
|
422
|
+
timer.stop
|
|
423
|
+
sync_reader.join(0.5) if sync_reader.alive?
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
SyncResult::REMOVE
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
#
|
|
430
|
+
# Determine if we should fallback to the next synchronizer.
|
|
431
|
+
#
|
|
432
|
+
# @param status [LaunchDarkly::Interfaces::DataSource::Status] Current data source status
|
|
433
|
+
# @return [Boolean] true if fallback condition is met
|
|
434
|
+
#
|
|
435
|
+
def fallback_condition(status)
|
|
436
|
+
interrupted_at_runtime = status.state == LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED &&
|
|
437
|
+
Time.now - status.state_since > 60 # 1 minute
|
|
438
|
+
cannot_initialize = status.state == LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING &&
|
|
439
|
+
Time.now - status.state_since > 10 # 10 seconds
|
|
440
|
+
|
|
441
|
+
interrupted_at_runtime || cannot_initialize
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
#
|
|
445
|
+
# Determine if we should recover to the primary synchronizer.
|
|
446
|
+
#
|
|
447
|
+
# @param status [LaunchDarkly::Interfaces::DataSource::Status] Current data source status
|
|
448
|
+
# @return [Boolean] true if recovery condition is met (healthy for too long)
|
|
449
|
+
#
|
|
450
|
+
def recovery_condition(status)
|
|
451
|
+
status.state == LaunchDarkly::Interfaces::DataSource::Status::VALID &&
|
|
452
|
+
Time.now - status.state_since > 300 # 5 minutes
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
#
|
|
456
|
+
# Monitor the data store status. If the store comes online and
|
|
457
|
+
# potentially has stale data, we should write our known state to it.
|
|
458
|
+
#
|
|
459
|
+
# @param data_store_status [LaunchDarkly::Interfaces::DataStore::Status] The store status
|
|
460
|
+
# @return [void]
|
|
461
|
+
#
|
|
462
|
+
def persistent_store_outage_recovery(data_store_status)
|
|
463
|
+
return unless data_store_status.available
|
|
464
|
+
return unless data_store_status.stale
|
|
465
|
+
|
|
466
|
+
err = @store.commit
|
|
467
|
+
@logger.error { "[LDClient] Failed to reinitialize data store: #{err.message}" } if err
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
end
|
|
472
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LaunchDarkly
|
|
4
|
+
module Impl
|
|
5
|
+
module DataSystem
|
|
6
|
+
#
|
|
7
|
+
# HttpConfigOptions contains HTTP connection configuration settings.
|
|
8
|
+
# This class is created by data source builders and passed to v2 Requesters/DataSources.
|
|
9
|
+
#
|
|
10
|
+
class HttpConfigOptions
|
|
11
|
+
# Generic HTTP defaults - base URIs live in the respective builders
|
|
12
|
+
DEFAULT_READ_TIMEOUT = 10
|
|
13
|
+
DEFAULT_CONNECT_TIMEOUT = 2
|
|
14
|
+
|
|
15
|
+
attr_reader :base_uri, :socket_factory, :read_timeout, :connect_timeout
|
|
16
|
+
|
|
17
|
+
#
|
|
18
|
+
# @param base_uri [String] The base URI for HTTP requests
|
|
19
|
+
# @param socket_factory [Object, nil] Optional socket factory for custom connections
|
|
20
|
+
# @param read_timeout [Float, nil] Read timeout in seconds (defaults to DEFAULT_READ_TIMEOUT)
|
|
21
|
+
# @param connect_timeout [Float, nil] Connect timeout in seconds (defaults to DEFAULT_CONNECT_TIMEOUT)
|
|
22
|
+
#
|
|
23
|
+
def initialize(base_uri:, socket_factory: nil, read_timeout: nil, connect_timeout: nil)
|
|
24
|
+
@base_uri = base_uri
|
|
25
|
+
@socket_factory = socket_factory
|
|
26
|
+
@read_timeout = read_timeout || DEFAULT_READ_TIMEOUT
|
|
27
|
+
@connect_timeout = connect_timeout || DEFAULT_CONNECT_TIMEOUT
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|