launchdarkly-server-sdk 8.13.0-java → 8.15.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/README.md +1 -1
- data/lib/ldclient-rb/config.rb +24 -3
- data/lib/ldclient-rb/data_system/polling_data_source_builder.rb +7 -5
- data/lib/ldclient-rb/impl/context_filter.rb +7 -7
- data/lib/ldclient-rb/impl/data_source/polling.rb +14 -1
- data/lib/ldclient-rb/impl/data_source/requestor.rb +13 -3
- data/lib/ldclient-rb/impl/data_source/stream.rb +2 -0
- data/lib/ldclient-rb/impl/data_source.rb +37 -0
- data/lib/ldclient-rb/impl/data_store/feature_store_client_wrapper.rb +12 -0
- data/lib/ldclient-rb/impl/data_store/store.rb +24 -0
- data/lib/ldclient-rb/impl/data_system/fdv1.rb +5 -0
- data/lib/ldclient-rb/impl/data_system/fdv2.rb +92 -13
- data/lib/ldclient-rb/impl/data_system/polling.rb +100 -33
- data/lib/ldclient-rb/impl/data_system/protocolv2.rb +3 -29
- data/lib/ldclient-rb/impl/data_system/streaming.rb +54 -31
- data/lib/ldclient-rb/impl/data_system.rb +15 -5
- data/lib/ldclient-rb/impl/integrations/file_data_source_v2.rb +29 -22
- data/lib/ldclient-rb/impl/integrations/redis_impl.rb +4 -0
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +53 -48
- data/lib/ldclient-rb/integrations/consul.rb +6 -2
- data/lib/ldclient-rb/integrations/dynamodb.rb +6 -2
- data/lib/ldclient-rb/integrations/file_data.rb +1 -4
- data/lib/ldclient-rb/integrations/redis.rb +6 -2
- data/lib/ldclient-rb/integrations/test_data_v2.rb +0 -3
- data/lib/ldclient-rb/integrations/util/store_wrapper.rb +39 -19
- data/lib/ldclient-rb/interfaces/data_system.rb +60 -57
- data/lib/ldclient-rb/interfaces/hooks.rb +11 -1
- data/lib/ldclient-rb/ldclient.rb +1 -1
- data/lib/ldclient-rb/util.rb +3 -2
- data/lib/ldclient-rb/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc18b4c7af16130b9f7e9f7c8d0a4acd8e562d16ebb7714fc7846c9ff96d1d3f
|
|
4
|
+
data.tar.gz: 2c9bfdff939fb1f11a9bc83e707aa0e550136004c4a2870b7220fb293ad268f2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b23cf9eda9b570844be07e71831f19734881b72d08c464b109929b17d225ac2083d78ead046418bdfec68e67db82f940c344efd3ce408ad1a1ea49e0d3dcaf2
|
|
7
|
+
data.tar.gz: ceb93287c34afbbb2301d3991267a10b1349aa126bea72c028552c431dc3493ec5f136d06b79f21641363dd9bfa87cce1a5ceb225534ff98ab8ad3088c2d7fc0
|
data/README.md
CHANGED
|
@@ -43,7 +43,7 @@ We encourage pull requests and other contributions from the community. Check out
|
|
|
43
43
|
Verifying SDK build provenance with the SLSA framework
|
|
44
44
|
------------
|
|
45
45
|
|
|
46
|
-
LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md).
|
|
46
|
+
LaunchDarkly uses the [SLSA framework](https://slsa.dev/spec/v1.0/about) (Supply-chain Levels for Software Artifacts) to help developers make their supply chain more secure by ensuring the authenticity and build integrity of our published SDK packages. To learn more, see the [provenance guide](PROVENANCE.md).
|
|
47
47
|
|
|
48
48
|
About LaunchDarkly
|
|
49
49
|
-----------
|
data/lib/ldclient-rb/config.rb
CHANGED
|
@@ -399,6 +399,30 @@ module LaunchDarkly
|
|
|
399
399
|
#
|
|
400
400
|
attr_reader :wrapper_version
|
|
401
401
|
|
|
402
|
+
#
|
|
403
|
+
# Returns a copy of this configuration with different wrapper information.
|
|
404
|
+
#
|
|
405
|
+
# This is intended for use by wrapper libraries, such as the LaunchDarkly OpenFeature provider, which need to
|
|
406
|
+
# identify themselves rather than the application which created the configuration.
|
|
407
|
+
#
|
|
408
|
+
# The copy is shallow: it shares the objects the original configuration references, such as the feature store,
|
|
409
|
+
# the logger, and the socket factory. Mutating one of those objects affects both configurations.
|
|
410
|
+
#
|
|
411
|
+
# @param wrapper_name [String, nil] see {#wrapper_name}
|
|
412
|
+
# @param wrapper_version [String, nil] see {#wrapper_version}
|
|
413
|
+
#
|
|
414
|
+
# @return [LaunchDarkly::Config]
|
|
415
|
+
#
|
|
416
|
+
def with_wrapper_information(wrapper_name, wrapper_version = nil)
|
|
417
|
+
updated = dup
|
|
418
|
+
updated.wrapper_name = wrapper_name
|
|
419
|
+
updated.wrapper_version = wrapper_version
|
|
420
|
+
|
|
421
|
+
updated
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
protected attr_writer :wrapper_name, :wrapper_version
|
|
425
|
+
|
|
402
426
|
#
|
|
403
427
|
# The factory used to construct sockets for HTTP operations. The factory must
|
|
404
428
|
# provide the method `open(uri, timeout)`. The `open` method must return a
|
|
@@ -697,9 +721,6 @@ module LaunchDarkly
|
|
|
697
721
|
#
|
|
698
722
|
# Configuration for LaunchDarkly's data acquisition strategy.
|
|
699
723
|
#
|
|
700
|
-
# This is not stable and is not subject to any backwards compatibility guarantees
|
|
701
|
-
# or semantic versioning. It is not suitable for production usage.
|
|
702
|
-
#
|
|
703
724
|
class DataSystemConfig
|
|
704
725
|
#
|
|
705
726
|
# @param initializers [Array<#build(String, Config)>, nil] The (optional) array of builders
|
|
@@ -21,9 +21,9 @@ module LaunchDarkly
|
|
|
21
21
|
# include LaunchDarkly::DataSystem::Requester
|
|
22
22
|
#
|
|
23
23
|
# def fetch(selector)
|
|
24
|
-
# # Fetch data and return a Result
|
|
25
|
-
# #
|
|
26
|
-
# LaunchDarkly::Result.success(
|
|
24
|
+
# # Fetch data and return a Result whose value is a ChangeSet and
|
|
25
|
+
# # whose headers carry any response metadata (e.g. directives).
|
|
26
|
+
# LaunchDarkly::Result.success(change_set, response_headers)
|
|
27
27
|
# end
|
|
28
28
|
#
|
|
29
29
|
# def stop
|
|
@@ -43,8 +43,10 @@ module LaunchDarkly
|
|
|
43
43
|
# @param selector [LaunchDarkly::Interfaces::DataSystem::Selector, nil]
|
|
44
44
|
# The selector describing what data to fetch. May be nil if no
|
|
45
45
|
# selector is available (e.g., on the first request).
|
|
46
|
-
# @return [LaunchDarkly::Result] A Result
|
|
47
|
-
#
|
|
46
|
+
# @return [LaunchDarkly::Result] A Result whose `value` is a
|
|
47
|
+
# {LaunchDarkly::Interfaces::DataSystem::ChangeSet} on success and
|
|
48
|
+
# whose `headers` carry response headers in either case (so callers
|
|
49
|
+
# can inspect directives such as `X-LD-FD-Fallback`).
|
|
48
50
|
#
|
|
49
51
|
def fetch(selector)
|
|
50
52
|
raise NotImplementedError
|
|
@@ -70,15 +70,15 @@ module LaunchDarkly
|
|
|
70
70
|
filtered[:anonymous] = true if anonymous
|
|
71
71
|
|
|
72
72
|
redacted = []
|
|
73
|
-
private_attributes = @private_attributes
|
|
73
|
+
private_attributes = @private_attributes + context.private_attributes
|
|
74
74
|
|
|
75
75
|
name = context.get_value(:name)
|
|
76
|
-
if !name.nil? && !check_whole_attribute_private(:name, private_attributes, redacted, anonymous && redact_anonymous)
|
|
76
|
+
if !name.nil? && !check_whole_attribute_private(Reference.create_literal(:name), private_attributes, redacted, anonymous && redact_anonymous)
|
|
77
77
|
filtered[:name] = name
|
|
78
78
|
end
|
|
79
79
|
|
|
80
80
|
context.get_custom_attribute_names.each do |attribute|
|
|
81
|
-
unless check_whole_attribute_private(attribute, private_attributes, redacted, anonymous && redact_anonymous)
|
|
81
|
+
unless check_whole_attribute_private(Reference.create_literal(attribute), private_attributes, redacted, anonymous && redact_anonymous)
|
|
82
82
|
value = context.get_value(attribute)
|
|
83
83
|
filtered[attribute] = redact_json_value(nil, attribute, value, private_attributes, redacted)
|
|
84
84
|
end
|
|
@@ -92,7 +92,7 @@ module LaunchDarkly
|
|
|
92
92
|
#
|
|
93
93
|
# Check if an entire attribute should be redacted.
|
|
94
94
|
#
|
|
95
|
-
# @param attribute [
|
|
95
|
+
# @param attribute [Reference]
|
|
96
96
|
# @param private_attributes [Array<Reference>]
|
|
97
97
|
# @param redacted [Array<Symbol>]
|
|
98
98
|
# @param redact_all [Boolean]
|
|
@@ -100,13 +100,13 @@ module LaunchDarkly
|
|
|
100
100
|
#
|
|
101
101
|
private def check_whole_attribute_private(attribute, private_attributes, redacted, redact_all)
|
|
102
102
|
if @all_attributes_private || redact_all
|
|
103
|
-
redacted << attribute
|
|
103
|
+
redacted << attribute.raw_path.to_sym
|
|
104
104
|
return true
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
private_attributes.each do |private_attribute|
|
|
108
|
-
if private_attribute.component(0) == attribute && private_attribute.depth == 1
|
|
109
|
-
redacted << attribute
|
|
108
|
+
if private_attribute.component(0) == attribute.component(0) && private_attribute.depth == 1
|
|
109
|
+
redacted << attribute.raw_path.to_sym
|
|
110
110
|
return true
|
|
111
111
|
end
|
|
112
112
|
end
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "ldclient-rb/impl/data_source"
|
|
1
2
|
require "ldclient-rb/impl/repeating_task"
|
|
2
3
|
require "ldclient-rb/impl/util"
|
|
3
4
|
|
|
@@ -35,7 +36,8 @@ module LaunchDarkly
|
|
|
35
36
|
|
|
36
37
|
def poll
|
|
37
38
|
begin
|
|
38
|
-
all_data =
|
|
39
|
+
all_data, headers = request_all_data
|
|
40
|
+
DataSource.record_environment_id(@config.data_source_update_sink, headers)
|
|
39
41
|
if all_data
|
|
40
42
|
update_sink_or_data_store.init(all_data)
|
|
41
43
|
if @initialized.make_true
|
|
@@ -92,6 +94,17 @@ module LaunchDarkly
|
|
|
92
94
|
@config.data_source_update_sink || @config.feature_store
|
|
93
95
|
end
|
|
94
96
|
|
|
97
|
+
#
|
|
98
|
+
# Requestors provided by application code may not be able to report the response headers.
|
|
99
|
+
#
|
|
100
|
+
# @return [Array(Hash, HTTP::Headers, nil)]
|
|
101
|
+
#
|
|
102
|
+
private def request_all_data
|
|
103
|
+
return @requestor.request_all_data_with_headers if @requestor.respond_to?(:request_all_data_with_headers)
|
|
104
|
+
|
|
105
|
+
[@requestor.request_all_data, nil]
|
|
106
|
+
end
|
|
107
|
+
|
|
95
108
|
#
|
|
96
109
|
# @param [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] error_info
|
|
97
110
|
#
|
|
@@ -40,8 +40,18 @@ module LaunchDarkly
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def request_all_data()
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
request_all_data_with_headers.first
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
#
|
|
47
|
+
# Requests the full data set, also returning the headers of the response it was retrieved from.
|
|
48
|
+
#
|
|
49
|
+
# @return [Array(Hash, HTTP::Headers)]
|
|
50
|
+
#
|
|
51
|
+
def request_all_data_with_headers
|
|
52
|
+
body, headers = make_request("/sdk/latest-all")
|
|
53
|
+
all_data = JSON.parse(body, symbolize_names: true)
|
|
54
|
+
[Impl::Model.make_all_store_data(all_data, @config.logger), headers]
|
|
45
55
|
end
|
|
46
56
|
|
|
47
57
|
def stop
|
|
@@ -80,7 +90,7 @@ module LaunchDarkly
|
|
|
80
90
|
etag = response.headers["etag"]
|
|
81
91
|
@cache.write(uri, CacheEntry.new(etag, body)) unless etag.nil?
|
|
82
92
|
end
|
|
83
|
-
body
|
|
93
|
+
[body, response.headers]
|
|
84
94
|
end
|
|
85
95
|
|
|
86
96
|
def fix_encoding(body, content_type)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require "ldclient-rb/impl/data_source"
|
|
1
2
|
require "ldclient-rb/impl/model/serialization"
|
|
2
3
|
require "ldclient-rb/impl/util"
|
|
3
4
|
require "ldclient-rb/in_memory_store"
|
|
@@ -54,6 +55,7 @@ module LaunchDarkly
|
|
|
54
55
|
|
|
55
56
|
uri = Impl::Util.add_payload_filter_key(@config.stream_uri + "/all", @config)
|
|
56
57
|
@es = SSE::Client.new(uri, **opts) do |conn|
|
|
58
|
+
conn.on_connect { |response_headers| DataSource.record_environment_id(@data_source_update_sink, response_headers) }
|
|
57
59
|
conn.on_event { |event| process_message(event) }
|
|
58
60
|
conn.on_error { |err|
|
|
59
61
|
log_connection_result(false)
|
|
@@ -7,6 +7,27 @@ require "set"
|
|
|
7
7
|
module LaunchDarkly
|
|
8
8
|
module Impl
|
|
9
9
|
module DataSource
|
|
10
|
+
LD_ENVID_HEADER = "X-LD-EnvID"
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Records the environment ID reported by LaunchDarkly, when the response headers provide one and the sink is
|
|
14
|
+
# able to hold it. Externally implemented sinks are not required to support this.
|
|
15
|
+
#
|
|
16
|
+
# @param sink [LaunchDarkly::Interfaces::DataSource::UpdateSink, nil]
|
|
17
|
+
# @param headers [#[], nil]
|
|
18
|
+
# @return [void]
|
|
19
|
+
#
|
|
20
|
+
def self.record_environment_id(sink, headers)
|
|
21
|
+
return if headers.nil? || !sink.respond_to?(:set_environment_id)
|
|
22
|
+
|
|
23
|
+
environment_id = headers[LD_ENVID_HEADER]
|
|
24
|
+
# The http gem returns arrays for repeated headers; normalize to a string.
|
|
25
|
+
environment_id = environment_id.first if environment_id.is_a?(Array)
|
|
26
|
+
return unless environment_id.is_a?(String) && !environment_id.empty?
|
|
27
|
+
|
|
28
|
+
sink.set_environment_id(environment_id)
|
|
29
|
+
end
|
|
30
|
+
|
|
10
31
|
class StatusProvider
|
|
11
32
|
include LaunchDarkly::Interfaces::DataSource::StatusProvider
|
|
12
33
|
|
|
@@ -41,12 +62,28 @@ module LaunchDarkly
|
|
|
41
62
|
@dependency_tracker = LaunchDarkly::Impl::DependencyTracker.new
|
|
42
63
|
|
|
43
64
|
@mutex = Mutex.new
|
|
65
|
+
@environment_id = nil
|
|
44
66
|
@current_status = LaunchDarkly::Interfaces::DataSource::Status.new(
|
|
45
67
|
LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
|
|
46
68
|
Time.now,
|
|
47
69
|
nil)
|
|
48
70
|
end
|
|
49
71
|
|
|
72
|
+
#
|
|
73
|
+
# @return [String, nil] The environment ID reported by LaunchDarkly, if known
|
|
74
|
+
#
|
|
75
|
+
def environment_id
|
|
76
|
+
@mutex.synchronize { @environment_id }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
# @param environment_id [String]
|
|
81
|
+
# @return [void]
|
|
82
|
+
#
|
|
83
|
+
def set_environment_id(environment_id)
|
|
84
|
+
@mutex.synchronize { @environment_id = environment_id }
|
|
85
|
+
end
|
|
86
|
+
|
|
50
87
|
def init(all_data)
|
|
51
88
|
old_data = nil
|
|
52
89
|
monitor_store_update do
|
|
@@ -65,6 +65,18 @@ module LaunchDarkly
|
|
|
65
65
|
@store.initialized?
|
|
66
66
|
end
|
|
67
67
|
|
|
68
|
+
#
|
|
69
|
+
# Passthrough for the FDv2 store coordinator. If the wrapped store supports
|
|
70
|
+
# disabling its cache (e.g. {LaunchDarkly::Integrations::Util::CachingStoreWrapper}),
|
|
71
|
+
# forward the call; otherwise no-op.
|
|
72
|
+
#
|
|
73
|
+
# @return [void]
|
|
74
|
+
#
|
|
75
|
+
def disable_cache
|
|
76
|
+
return unless @store.respond_to?(:disable_cache)
|
|
77
|
+
wrapper { @store.disable_cache }
|
|
78
|
+
end
|
|
79
|
+
|
|
68
80
|
# (see LaunchDarkly::Interfaces::FeatureStore#stop)
|
|
69
81
|
def stop
|
|
70
82
|
poller_to_stop = nil
|
|
@@ -221,6 +221,12 @@ module LaunchDarkly
|
|
|
221
221
|
# Switch to memory store as active
|
|
222
222
|
@active_store = @memory_store
|
|
223
223
|
|
|
224
|
+
# In-memory store is now authoritative. Disable the persistent-store cache
|
|
225
|
+
# so we don't hold a duplicate copy of every flag. Done before the
|
|
226
|
+
# persistent_store.init below so the wrapper can skip its cache-population
|
|
227
|
+
# loop on this very call.
|
|
228
|
+
disable_persistent_cache
|
|
229
|
+
|
|
224
230
|
# Persist to persistent store if configured and writable
|
|
225
231
|
@persistent_store.init(collections) if should_persist?
|
|
226
232
|
|
|
@@ -271,6 +277,24 @@ module LaunchDarkly
|
|
|
271
277
|
send_change_events(affected_items) unless affected_items.empty?
|
|
272
278
|
end
|
|
273
279
|
|
|
280
|
+
#
|
|
281
|
+
# Disable the persistent store's in-memory cache, if it has one. The FDv2 in-memory
|
|
282
|
+
# store is now the source of truth, so the cache is dead weight and would just
|
|
283
|
+
# hold a duplicate copy of every flag.
|
|
284
|
+
#
|
|
285
|
+
# @return [void]
|
|
286
|
+
#
|
|
287
|
+
private def disable_persistent_cache
|
|
288
|
+
return if @persistent_store.nil?
|
|
289
|
+
return unless @persistent_store.respond_to?(:disable_cache)
|
|
290
|
+
|
|
291
|
+
begin
|
|
292
|
+
@persistent_store.disable_cache
|
|
293
|
+
rescue => e
|
|
294
|
+
@logger.warn { "[LDClient] Failed to disable persistent store cache: #{e.message}" }
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
274
298
|
#
|
|
275
299
|
# Returns whether data should be persisted to the persistent store.
|
|
276
300
|
#
|
|
@@ -100,6 +100,9 @@ module LaunchDarkly
|
|
|
100
100
|
@store.with_persistence(wrapper, writable, @data_store_status_provider)
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
+
@environment_id = nil
|
|
104
|
+
@environment_id_lock = Mutex.new
|
|
105
|
+
|
|
103
106
|
# Threading
|
|
104
107
|
@stop_event = Concurrent::Event.new
|
|
105
108
|
@ready_event = Concurrent::Event.new
|
|
@@ -185,6 +188,11 @@ module LaunchDarkly
|
|
|
185
188
|
@flag_change_broadcaster
|
|
186
189
|
end
|
|
187
190
|
|
|
191
|
+
# (see DataSystem#environment_id)
|
|
192
|
+
def environment_id
|
|
193
|
+
@environment_id_lock.synchronize { @environment_id }
|
|
194
|
+
end
|
|
195
|
+
|
|
188
196
|
# (see DataSystem#data_availability)
|
|
189
197
|
def data_availability
|
|
190
198
|
return DataAvailability::REFRESHED if @store.selector.defined?
|
|
@@ -214,8 +222,23 @@ module LaunchDarkly
|
|
|
214
222
|
nil
|
|
215
223
|
)
|
|
216
224
|
|
|
217
|
-
# Run initializers first
|
|
218
|
-
|
|
225
|
+
# Run initializers first. If an initializer signals the
|
|
226
|
+
# server-directed FDv1 Fallback Directive, switch terminally to
|
|
227
|
+
# the FDv1 Fallback Synchronizer (or transition to OFF if none
|
|
228
|
+
# is configured) before entering the synchronizer phase.
|
|
229
|
+
if run_initializers
|
|
230
|
+
if @fdv1_fallback_synchronizer_builder
|
|
231
|
+
@logger.warn { "[LDClient] Falling back to FDv1 protocol" }
|
|
232
|
+
@synchronizer_builders = [@fdv1_fallback_synchronizer_builder]
|
|
233
|
+
else
|
|
234
|
+
@logger.warn { "[LDClient] Initializer requested FDv1 fallback but none configured" }
|
|
235
|
+
@synchronizer_builders = []
|
|
236
|
+
@data_source_status_provider.update_status(
|
|
237
|
+
LaunchDarkly::Interfaces::DataSource::Status::OFF,
|
|
238
|
+
@data_source_status_provider.status.last_error
|
|
239
|
+
)
|
|
240
|
+
end
|
|
241
|
+
end
|
|
219
242
|
|
|
220
243
|
# Run synchronizers
|
|
221
244
|
run_synchronizers
|
|
@@ -228,39 +251,71 @@ module LaunchDarkly
|
|
|
228
251
|
#
|
|
229
252
|
# Run initializers to get initial data.
|
|
230
253
|
#
|
|
231
|
-
#
|
|
254
|
+
# Each initializer is tried in order until one succeeds, the system
|
|
255
|
+
# is stopped, or an initializer signals the server-directed FDv1
|
|
256
|
+
# Fallback Directive. When fallback is signalled alongside a valid
|
|
257
|
+
# payload, that payload is applied before returning so evaluations
|
|
258
|
+
# can serve the server-provided data while the FDv1 synchronizer
|
|
259
|
+
# spins up. The method returns true when fallback was requested so
|
|
260
|
+
# that the caller can switch the synchronizer list.
|
|
261
|
+
#
|
|
262
|
+
# @return [Boolean] true when an initializer requested FDv1 fallback.
|
|
232
263
|
#
|
|
233
264
|
def run_initializers
|
|
234
|
-
return unless @data_system_config.initializers
|
|
265
|
+
return false unless @data_system_config.initializers
|
|
235
266
|
|
|
236
267
|
@data_system_config.initializers.each do |initializer_builder|
|
|
237
|
-
return if @stop_event.set?
|
|
268
|
+
return false if @stop_event.set?
|
|
238
269
|
|
|
239
270
|
begin
|
|
240
271
|
initializer = initializer_builder.build(@sdk_key, @config)
|
|
241
272
|
@logger.info { "[LDClient] Attempting to initialize via #{initializer.name}" }
|
|
242
273
|
|
|
243
|
-
|
|
274
|
+
fetch_result = initializer.fetch(@store)
|
|
275
|
+
fallback = fetch_result.fallback_to_fdv1
|
|
276
|
+
basis_result = fetch_result.result
|
|
244
277
|
|
|
245
278
|
if basis_result.success?
|
|
246
279
|
basis = basis_result.value
|
|
247
280
|
@logger.info { "[LDClient] Initialized via #{initializer.name}" }
|
|
281
|
+
record_environment_id(basis.environment_id)
|
|
248
282
|
|
|
249
|
-
# Apply the basis to the store
|
|
283
|
+
# Apply the basis to the store regardless of whether fallback was signalled.
|
|
284
|
+
# If the server returned a valid payload alongside the directive we still want
|
|
285
|
+
# evaluations to serve that data while the FDv1 synchronizer spins up.
|
|
250
286
|
@store.apply(basis.change_set, basis.persist)
|
|
251
287
|
|
|
252
|
-
# Set ready event if and only if a selector is defined for the changeset
|
|
288
|
+
# Set ready event if and only if a selector is defined for the changeset.
|
|
253
289
|
if basis.change_set.selector && basis.change_set.selector.defined?
|
|
254
290
|
@ready_event.set
|
|
255
|
-
return
|
|
291
|
+
return fallback
|
|
256
292
|
end
|
|
257
293
|
else
|
|
258
294
|
@logger.warn { "[LDClient] Initializer #{initializer.name} failed: #{basis_result.error}" }
|
|
295
|
+
if fallback
|
|
296
|
+
# Record the underlying initializer error so that, if no FDv1 fallback is
|
|
297
|
+
# configured, the subsequent transition to OFF carries it as last_error.
|
|
298
|
+
@data_source_status_provider.update_status(
|
|
299
|
+
LaunchDarkly::Interfaces::DataSource::Status::INITIALIZING,
|
|
300
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(
|
|
301
|
+
LaunchDarkly::Interfaces::DataSource::ErrorInfo::UNKNOWN,
|
|
302
|
+
0,
|
|
303
|
+
basis_result.error || "",
|
|
304
|
+
Time.now
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
end
|
|
259
308
|
end
|
|
309
|
+
|
|
310
|
+
# The fallback directive takes precedence over the regular failover algorithm,
|
|
311
|
+
# so do not fall through to the next initializer when it is set.
|
|
312
|
+
return true if fallback
|
|
260
313
|
rescue => e
|
|
261
314
|
@logger.error { "[LDClient] Initializer failed with exception: #{e.message}" }
|
|
262
315
|
end
|
|
263
316
|
end
|
|
317
|
+
|
|
318
|
+
false
|
|
264
319
|
end
|
|
265
320
|
|
|
266
321
|
#
|
|
@@ -313,12 +368,21 @@ module LaunchDarkly
|
|
|
313
368
|
case sync_result
|
|
314
369
|
when SyncResult::FDV1
|
|
315
370
|
if @fdv1_fallback_synchronizer_builder
|
|
371
|
+
@logger.warn { "[LDClient] Falling back to FDv1 protocol" }
|
|
316
372
|
@synchronizer_builders = [@fdv1_fallback_synchronizer_builder]
|
|
317
373
|
current_index = 0
|
|
318
374
|
next
|
|
319
375
|
end
|
|
320
|
-
# No FDv1 fallback configured
|
|
321
|
-
|
|
376
|
+
# No FDv1 fallback configured: the data system must HALT
|
|
377
|
+
# rather than fall through to the next FDv2 synchronizer.
|
|
378
|
+
# Continuing to retry would reopen the connection that just
|
|
379
|
+
# delivered the directive.
|
|
380
|
+
@logger.warn { "[LDClient] Synchronizer requested FDv1 fallback but none configured; halting data system" }
|
|
381
|
+
@data_source_status_provider.update_status(
|
|
382
|
+
LaunchDarkly::Interfaces::DataSource::Status::OFF,
|
|
383
|
+
@data_source_status_provider.status.last_error
|
|
384
|
+
)
|
|
385
|
+
break
|
|
322
386
|
when SyncResult::RECOVER
|
|
323
387
|
@logger.info { "[LDClient] Recovery condition met, returning to primary synchronizer" }
|
|
324
388
|
current_index = 0
|
|
@@ -405,12 +469,15 @@ module LaunchDarkly
|
|
|
405
469
|
@store.apply(update.change_set, true) if update.change_set
|
|
406
470
|
|
|
407
471
|
# Set ready event on valid update
|
|
408
|
-
|
|
472
|
+
if update.state == LaunchDarkly::Interfaces::DataSource::Status::VALID
|
|
473
|
+
@ready_event.set
|
|
474
|
+
record_environment_id(update.environment_id)
|
|
475
|
+
end
|
|
409
476
|
|
|
410
477
|
# Update status
|
|
411
478
|
@data_source_status_provider.update_status(update.state, update.error)
|
|
412
479
|
|
|
413
|
-
return SyncResult::FDV1 if update.
|
|
480
|
+
return SyncResult::FDV1 if update.fallback_to_fdv1
|
|
414
481
|
|
|
415
482
|
return SyncResult::REMOVE if update.state == LaunchDarkly::Interfaces::DataSource::Status::OFF
|
|
416
483
|
end
|
|
@@ -426,6 +493,18 @@ module LaunchDarkly
|
|
|
426
493
|
SyncResult::REMOVE
|
|
427
494
|
end
|
|
428
495
|
|
|
496
|
+
#
|
|
497
|
+
# Retains the environment ID reported by a successful connection to LaunchDarkly.
|
|
498
|
+
#
|
|
499
|
+
# @param environment_id [String, nil]
|
|
500
|
+
# @return [void]
|
|
501
|
+
#
|
|
502
|
+
def record_environment_id(environment_id)
|
|
503
|
+
return unless environment_id.is_a?(String) && !environment_id.empty?
|
|
504
|
+
|
|
505
|
+
@environment_id_lock.synchronize { @environment_id = environment_id }
|
|
506
|
+
end
|
|
507
|
+
|
|
429
508
|
#
|
|
430
509
|
# Determine if we should fallback to the next synchronizer.
|
|
431
510
|
#
|