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