launchdarkly-server-sdk 8.11.1 → 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 +35 -20
- 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/null_processor.rb +52 -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 +178 -0
- 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/data_system.rb +298 -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 +9 -9
- data/lib/ldclient-rb/impl/integrations/test_data/test_data_source.rb +0 -1
- 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 -149
- data/lib/ldclient-rb/util.rb +11 -70
- data/lib/ldclient-rb/version.rb +1 -1
- data/lib/ldclient-rb.rb +8 -17
- metadata +52 -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 -196
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module LaunchDarkly
|
|
4
|
+
module Impl
|
|
5
|
+
module DataSystem
|
|
6
|
+
module ProtocolV2
|
|
7
|
+
#
|
|
8
|
+
# This module contains the protocol definitions and data types for the
|
|
9
|
+
# LaunchDarkly data system version 2 (FDv2).
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# DeleteObject specifies the deletion of a particular object.
|
|
14
|
+
#
|
|
15
|
+
# This type is not stable, and not subject to any backwards
|
|
16
|
+
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
17
|
+
#
|
|
18
|
+
class DeleteObject
|
|
19
|
+
# @return [Integer] The version
|
|
20
|
+
attr_reader :version
|
|
21
|
+
|
|
22
|
+
# @return [String] The object kind ({LaunchDarkly::Interfaces::DataSystem::ObjectKind})
|
|
23
|
+
attr_reader :kind
|
|
24
|
+
|
|
25
|
+
# @return [String] The key
|
|
26
|
+
attr_reader :key
|
|
27
|
+
|
|
28
|
+
#
|
|
29
|
+
# @param version [Integer] The version
|
|
30
|
+
# @param kind [String] The object kind ({LaunchDarkly::Interfaces::DataSystem::ObjectKind})
|
|
31
|
+
# @param key [String] The key
|
|
32
|
+
#
|
|
33
|
+
def initialize(version:, kind:, key:)
|
|
34
|
+
@version = version
|
|
35
|
+
@kind = kind
|
|
36
|
+
@key = key
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
# Returns the event name.
|
|
41
|
+
#
|
|
42
|
+
# @return [String]
|
|
43
|
+
#
|
|
44
|
+
def name
|
|
45
|
+
LaunchDarkly::Interfaces::DataSystem::EventName::DELETE_OBJECT
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
#
|
|
49
|
+
# Serializes the DeleteObject to a JSON-compatible hash.
|
|
50
|
+
#
|
|
51
|
+
# @return [Hash]
|
|
52
|
+
#
|
|
53
|
+
def to_h
|
|
54
|
+
{
|
|
55
|
+
version: @version,
|
|
56
|
+
kind: @kind,
|
|
57
|
+
key: @key,
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
#
|
|
62
|
+
# Deserializes a DeleteObject from a JSON-compatible hash.
|
|
63
|
+
#
|
|
64
|
+
# @param data [Hash] The hash representation
|
|
65
|
+
# @return [DeleteObject]
|
|
66
|
+
# @raise [ArgumentError] if required fields are missing
|
|
67
|
+
#
|
|
68
|
+
def self.from_h(data)
|
|
69
|
+
version = data[:version]
|
|
70
|
+
kind = data[:kind]
|
|
71
|
+
key = data[:key]
|
|
72
|
+
|
|
73
|
+
raise ArgumentError, "Missing required fields in DeleteObject" if version.nil? || kind.nil? || key.nil?
|
|
74
|
+
|
|
75
|
+
new(version: version, kind: kind, key: key)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
#
|
|
80
|
+
# PutObject specifies the addition of a particular object with upsert semantics.
|
|
81
|
+
#
|
|
82
|
+
# This type is not stable, and not subject to any backwards
|
|
83
|
+
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
84
|
+
#
|
|
85
|
+
class PutObject
|
|
86
|
+
# @return [Integer] The version
|
|
87
|
+
attr_reader :version
|
|
88
|
+
|
|
89
|
+
# @return [String] The object kind ({LaunchDarkly::Interfaces::DataSystem::ObjectKind})
|
|
90
|
+
attr_reader :kind
|
|
91
|
+
|
|
92
|
+
# @return [String] The key
|
|
93
|
+
attr_reader :key
|
|
94
|
+
|
|
95
|
+
# @return [Hash] The object data
|
|
96
|
+
attr_reader :object
|
|
97
|
+
|
|
98
|
+
#
|
|
99
|
+
# @param version [Integer] The version
|
|
100
|
+
# @param kind [String] The object kind ({LaunchDarkly::Interfaces::DataSystem::ObjectKind})
|
|
101
|
+
# @param key [String] The key
|
|
102
|
+
# @param object [Hash] The object data
|
|
103
|
+
#
|
|
104
|
+
def initialize(version:, kind:, key:, object:)
|
|
105
|
+
@version = version
|
|
106
|
+
@kind = kind
|
|
107
|
+
@key = key
|
|
108
|
+
@object = object
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# Returns the event name.
|
|
113
|
+
#
|
|
114
|
+
# @return [String]
|
|
115
|
+
#
|
|
116
|
+
def name
|
|
117
|
+
LaunchDarkly::Interfaces::DataSystem::EventName::PUT_OBJECT
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
#
|
|
121
|
+
# Serializes the PutObject to a JSON-compatible hash.
|
|
122
|
+
#
|
|
123
|
+
# @return [Hash]
|
|
124
|
+
#
|
|
125
|
+
def to_h
|
|
126
|
+
{
|
|
127
|
+
version: @version,
|
|
128
|
+
kind: @kind,
|
|
129
|
+
key: @key,
|
|
130
|
+
object: @object,
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
#
|
|
135
|
+
# Deserializes a PutObject from a JSON-compatible hash.
|
|
136
|
+
#
|
|
137
|
+
# @param data [Hash] The hash representation
|
|
138
|
+
# @return [PutObject]
|
|
139
|
+
# @raise [ArgumentError] if required fields are missing
|
|
140
|
+
#
|
|
141
|
+
def self.from_h(data)
|
|
142
|
+
version = data[:version]
|
|
143
|
+
kind = data[:kind]
|
|
144
|
+
key = data[:key]
|
|
145
|
+
object_data = data[:object]
|
|
146
|
+
|
|
147
|
+
raise ArgumentError, "Missing required fields in PutObject" if version.nil? || kind.nil? || key.nil? || object_data.nil?
|
|
148
|
+
|
|
149
|
+
new(version: version, kind: kind, key: key, object: object_data)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
#
|
|
154
|
+
# Goodbye represents a goodbye event.
|
|
155
|
+
#
|
|
156
|
+
# This type is not stable, and not subject to any backwards
|
|
157
|
+
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
158
|
+
#
|
|
159
|
+
class Goodbye
|
|
160
|
+
# @return [String] The reason for goodbye
|
|
161
|
+
attr_reader :reason
|
|
162
|
+
|
|
163
|
+
# @return [Boolean] Whether the goodbye is silent
|
|
164
|
+
attr_reader :silent
|
|
165
|
+
|
|
166
|
+
# @return [Boolean] Whether this represents a catastrophic failure
|
|
167
|
+
attr_reader :catastrophe
|
|
168
|
+
|
|
169
|
+
#
|
|
170
|
+
# @param reason [String] The reason for goodbye
|
|
171
|
+
# @param silent [Boolean] Whether the goodbye is silent
|
|
172
|
+
# @param catastrophe [Boolean] Whether this represents a catastrophic failure
|
|
173
|
+
#
|
|
174
|
+
def initialize(reason:, silent:, catastrophe:)
|
|
175
|
+
@reason = reason
|
|
176
|
+
@silent = silent
|
|
177
|
+
@catastrophe = catastrophe
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
#
|
|
181
|
+
# Serializes the Goodbye to a JSON-compatible hash.
|
|
182
|
+
#
|
|
183
|
+
# @return [Hash]
|
|
184
|
+
#
|
|
185
|
+
def to_h
|
|
186
|
+
{
|
|
187
|
+
reason: @reason,
|
|
188
|
+
silent: @silent,
|
|
189
|
+
catastrophe: @catastrophe,
|
|
190
|
+
}
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
#
|
|
194
|
+
# Deserializes a Goodbye event from a JSON-compatible hash.
|
|
195
|
+
#
|
|
196
|
+
# @param data [Hash] The hash representation
|
|
197
|
+
# @return [Goodbye]
|
|
198
|
+
# @raise [ArgumentError] if required fields are missing
|
|
199
|
+
#
|
|
200
|
+
def self.from_h(data)
|
|
201
|
+
reason = data[:reason]
|
|
202
|
+
silent = data[:silent]
|
|
203
|
+
catastrophe = data[:catastrophe]
|
|
204
|
+
|
|
205
|
+
raise ArgumentError, "Missing required fields in Goodbye" if reason.nil? || silent.nil? || catastrophe.nil?
|
|
206
|
+
|
|
207
|
+
new(reason: reason, silent: silent, catastrophe: catastrophe)
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
#
|
|
212
|
+
# Error represents an error event.
|
|
213
|
+
#
|
|
214
|
+
# This type is not stable, and not subject to any backwards
|
|
215
|
+
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
216
|
+
#
|
|
217
|
+
class Error
|
|
218
|
+
# @return [String] The payload ID
|
|
219
|
+
attr_reader :payload_id
|
|
220
|
+
|
|
221
|
+
# @return [String] The reason for the error
|
|
222
|
+
attr_reader :reason
|
|
223
|
+
|
|
224
|
+
#
|
|
225
|
+
# @param payload_id [String] The payload ID
|
|
226
|
+
# @param reason [String] The reason for the error
|
|
227
|
+
#
|
|
228
|
+
def initialize(payload_id:, reason:)
|
|
229
|
+
@payload_id = payload_id
|
|
230
|
+
@reason = reason
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
#
|
|
234
|
+
# Serializes the Error to a JSON-compatible hash.
|
|
235
|
+
#
|
|
236
|
+
# @return [Hash]
|
|
237
|
+
#
|
|
238
|
+
def to_h
|
|
239
|
+
{
|
|
240
|
+
payloadId: @payload_id,
|
|
241
|
+
reason: @reason,
|
|
242
|
+
}
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
#
|
|
246
|
+
# Deserializes an Error from a JSON-compatible hash.
|
|
247
|
+
#
|
|
248
|
+
# @param data [Hash] The hash representation
|
|
249
|
+
# @return [Error]
|
|
250
|
+
# @raise [ArgumentError] if required fields are missing
|
|
251
|
+
#
|
|
252
|
+
def self.from_h(data)
|
|
253
|
+
payload_id = data[:payloadId]
|
|
254
|
+
reason = data[:reason]
|
|
255
|
+
|
|
256
|
+
raise ArgumentError, "Missing required fields in Error" if payload_id.nil? || reason.nil?
|
|
257
|
+
|
|
258
|
+
new(payload_id: payload_id, reason: reason)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
module LaunchDarkly
|
|
2
|
+
module Impl
|
|
3
|
+
#
|
|
4
|
+
# Mixin that defines the required methods of a data system implementation. The data system
|
|
5
|
+
# is responsible for managing the SDK's data model, including storage, retrieval, and change
|
|
6
|
+
# detection for feature flag configurations.
|
|
7
|
+
#
|
|
8
|
+
# This module also contains supporting classes and additional mixins for data system
|
|
9
|
+
# implementations, such as DataAvailability, Update, and protocol-specific mixins.
|
|
10
|
+
#
|
|
11
|
+
# For operations that can fail, use {LaunchDarkly::Result} from util.rb.
|
|
12
|
+
#
|
|
13
|
+
# Application code should not need to implement this directly; it is used internally by the
|
|
14
|
+
# SDK's data system implementations.
|
|
15
|
+
#
|
|
16
|
+
# @private
|
|
17
|
+
#
|
|
18
|
+
module DataSystem
|
|
19
|
+
#
|
|
20
|
+
# Starts the data system.
|
|
21
|
+
#
|
|
22
|
+
# This method will return immediately. The returned event will be set when the system
|
|
23
|
+
# has reached an initial state (either permanently failed, e.g. due to bad auth, or succeeded).
|
|
24
|
+
#
|
|
25
|
+
# If called multiple times, returns the same event as the first call.
|
|
26
|
+
#
|
|
27
|
+
# @return [Concurrent::Event] Event that will be set when initialization is complete
|
|
28
|
+
#
|
|
29
|
+
def start
|
|
30
|
+
raise NotImplementedError, "#{self.class} must implement #start"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Halts the data system. Should be called when the client is closed to stop any long running
|
|
35
|
+
# operations. Makes the data system no longer usable.
|
|
36
|
+
#
|
|
37
|
+
# @return [void]
|
|
38
|
+
#
|
|
39
|
+
def stop
|
|
40
|
+
raise NotImplementedError, "#{self.class} must implement #stop"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Returns an interface for tracking the status of the data source.
|
|
45
|
+
#
|
|
46
|
+
# The data source is the mechanism that the SDK uses to get feature flag configurations, such
|
|
47
|
+
# as a streaming connection (the default) or poll requests.
|
|
48
|
+
#
|
|
49
|
+
# @return [LaunchDarkly::Interfaces::DataSource::StatusProvider]
|
|
50
|
+
#
|
|
51
|
+
def data_source_status_provider
|
|
52
|
+
raise NotImplementedError, "#{self.class} must implement #data_source_status_provider"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
#
|
|
56
|
+
# Returns an interface for tracking the status of a persistent data store.
|
|
57
|
+
#
|
|
58
|
+
# The provider has methods for checking whether the data store is (as far
|
|
59
|
+
# as the SDK knows) currently operational, tracking changes in this
|
|
60
|
+
# status, and getting cache statistics. These are only relevant for a
|
|
61
|
+
# persistent data store; if you are using an in-memory data store, then
|
|
62
|
+
# this method will return a stub object that provides no information.
|
|
63
|
+
#
|
|
64
|
+
# @return [LaunchDarkly::Interfaces::DataStore::StatusProvider]
|
|
65
|
+
#
|
|
66
|
+
def data_store_status_provider
|
|
67
|
+
raise NotImplementedError, "#{self.class} must implement #data_store_status_provider"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
#
|
|
71
|
+
# Returns the broadcaster for flag change notifications.
|
|
72
|
+
#
|
|
73
|
+
# Consumers can use this broadcaster to build their own flag tracker
|
|
74
|
+
# or listen for flag changes directly.
|
|
75
|
+
#
|
|
76
|
+
# @return [LaunchDarkly::Impl::Broadcaster]
|
|
77
|
+
#
|
|
78
|
+
def flag_change_broadcaster
|
|
79
|
+
raise NotImplementedError, "#{self.class} must implement #flag_change_broadcaster"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
#
|
|
83
|
+
# Indicates what form of data is currently available.
|
|
84
|
+
#
|
|
85
|
+
# This is calculated dynamically based on current system state.
|
|
86
|
+
#
|
|
87
|
+
# @return [Symbol] one of the {DataAvailability} constants
|
|
88
|
+
#
|
|
89
|
+
def data_availability
|
|
90
|
+
raise NotImplementedError, "#{self.class} must implement #data_availability"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
#
|
|
94
|
+
# Indicates the ideal form of data attainable given the current configuration.
|
|
95
|
+
#
|
|
96
|
+
# @return [Symbol] one of the {#DataAvailability} constants
|
|
97
|
+
#
|
|
98
|
+
def target_availability
|
|
99
|
+
raise NotImplementedError, "#{self.class} must implement #target_availability"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
#
|
|
103
|
+
# Returns the data store used by the data system.
|
|
104
|
+
#
|
|
105
|
+
# @return [Object] The read-only store
|
|
106
|
+
#
|
|
107
|
+
def store
|
|
108
|
+
raise NotImplementedError, "#{self.class} must implement #store"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
#
|
|
112
|
+
# Sets the diagnostic accumulator for streaming initialization metrics.
|
|
113
|
+
# This should be called before start() to ensure metrics are collected.
|
|
114
|
+
#
|
|
115
|
+
# @param diagnostic_accumulator [DiagnosticAccumulator] The diagnostic accumulator
|
|
116
|
+
# @return [void]
|
|
117
|
+
#
|
|
118
|
+
def set_diagnostic_accumulator(diagnostic_accumulator)
|
|
119
|
+
raise NotImplementedError, "#{self.class} must implement #set_diagnostic_accumulator"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
#
|
|
123
|
+
# Represents the availability of data in the SDK.
|
|
124
|
+
#
|
|
125
|
+
class DataAvailability
|
|
126
|
+
# The SDK has no data and will evaluate flags using the application-provided default values.
|
|
127
|
+
DEFAULTS = :defaults
|
|
128
|
+
|
|
129
|
+
# The SDK has data, not necessarily the latest, which will be used to evaluate flags.
|
|
130
|
+
CACHED = :cached
|
|
131
|
+
|
|
132
|
+
# The SDK has obtained, at least once, the latest known data from LaunchDarkly.
|
|
133
|
+
REFRESHED = :refreshed
|
|
134
|
+
|
|
135
|
+
ALL = [DEFAULTS, CACHED, REFRESHED].freeze
|
|
136
|
+
|
|
137
|
+
#
|
|
138
|
+
# Returns whether this availability level is **at least** as good as the other.
|
|
139
|
+
#
|
|
140
|
+
# @param [Symbol] self_level The current availability level
|
|
141
|
+
# @param [Symbol] other The other availability level to compare against
|
|
142
|
+
# @return [Boolean] true if this availability level is at least as good as the other
|
|
143
|
+
#
|
|
144
|
+
def self.at_least?(self_level, other)
|
|
145
|
+
return true if self_level == other
|
|
146
|
+
return true if self_level == REFRESHED
|
|
147
|
+
return true if self_level == CACHED && other == DEFAULTS
|
|
148
|
+
|
|
149
|
+
false
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
#
|
|
154
|
+
# Mixin that defines the required methods of a diagnostic accumulator implementation.
|
|
155
|
+
# The diagnostic accumulator is used for collecting and reporting diagnostic events
|
|
156
|
+
# to LaunchDarkly for monitoring SDK performance and behavior.
|
|
157
|
+
#
|
|
158
|
+
# Application code should not need to implement this directly; it is used internally by the SDK.
|
|
159
|
+
#
|
|
160
|
+
module DiagnosticAccumulator
|
|
161
|
+
#
|
|
162
|
+
# Record a stream initialization.
|
|
163
|
+
#
|
|
164
|
+
# @param timestamp [Float] The timestamp
|
|
165
|
+
# @param duration [Float] The duration
|
|
166
|
+
# @param failed [Boolean] Whether it failed
|
|
167
|
+
# @return [void]
|
|
168
|
+
#
|
|
169
|
+
def record_stream_init(timestamp, duration, failed)
|
|
170
|
+
raise NotImplementedError, "#{self.class} must implement #record_stream_init"
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#
|
|
174
|
+
# Record events in a batch.
|
|
175
|
+
#
|
|
176
|
+
# @param events_in_batch [Integer] The number of events
|
|
177
|
+
# @return [void]
|
|
178
|
+
#
|
|
179
|
+
def record_events_in_batch(events_in_batch)
|
|
180
|
+
raise NotImplementedError, "#{self.class} must implement #record_events_in_batch"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
#
|
|
184
|
+
# Create an event and reset the accumulator.
|
|
185
|
+
#
|
|
186
|
+
# @param dropped_events [Integer] The number of dropped events
|
|
187
|
+
# @param deduplicated_users [Integer] The number of deduplicated users
|
|
188
|
+
# @return [Object] The diagnostic event
|
|
189
|
+
#
|
|
190
|
+
def create_event_and_reset(dropped_events, deduplicated_users)
|
|
191
|
+
raise NotImplementedError, "#{self.class} must implement #create_event_and_reset"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
#
|
|
196
|
+
# Mixin that defines the required methods for components that can receive a diagnostic accumulator.
|
|
197
|
+
# Components that include this mixin can report diagnostic information to LaunchDarkly for
|
|
198
|
+
# monitoring SDK performance and behavior.
|
|
199
|
+
#
|
|
200
|
+
# Application code should not need to implement this directly; it is used internally by the SDK.
|
|
201
|
+
#
|
|
202
|
+
module DiagnosticSource
|
|
203
|
+
#
|
|
204
|
+
# Set the diagnostic_accumulator to be used for reporting diagnostic events.
|
|
205
|
+
#
|
|
206
|
+
# @param diagnostic_accumulator [DiagnosticAccumulator] The accumulator
|
|
207
|
+
# @return [void]
|
|
208
|
+
#
|
|
209
|
+
def set_diagnostic_accumulator(diagnostic_accumulator)
|
|
210
|
+
raise NotImplementedError, "#{self.class} must implement #set_diagnostic_accumulator"
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
#
|
|
215
|
+
# Mixin that defines the required methods of an initializer implementation. An initializer
|
|
216
|
+
# is a component capable of retrieving a single data result, such as from the LaunchDarkly
|
|
217
|
+
# polling API.
|
|
218
|
+
#
|
|
219
|
+
# The intent of initializers is to quickly fetch an initial set of data, which may be stale
|
|
220
|
+
# but is fast to retrieve. This initial data serves as a foundation for a Synchronizer to
|
|
221
|
+
# build upon, enabling it to provide updates as new changes occur.
|
|
222
|
+
#
|
|
223
|
+
# Application code should not need to implement this directly; it is used internally by the SDK.
|
|
224
|
+
#
|
|
225
|
+
module Initializer
|
|
226
|
+
#
|
|
227
|
+
# Fetch should retrieve the initial data set for the data source, returning
|
|
228
|
+
# a Basis object on success, or an error message on failure.
|
|
229
|
+
#
|
|
230
|
+
# @return [LaunchDarkly::Result] A Result containing either a Basis or an error message
|
|
231
|
+
#
|
|
232
|
+
def fetch
|
|
233
|
+
raise NotImplementedError, "#{self.class} must implement #fetch"
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
#
|
|
238
|
+
# Update represents the results of a synchronizer's ongoing sync method.
|
|
239
|
+
#
|
|
240
|
+
class Update
|
|
241
|
+
# @return [Symbol] The state of the data source
|
|
242
|
+
attr_reader :state
|
|
243
|
+
|
|
244
|
+
# @return [ChangeSet, nil] The change set if available
|
|
245
|
+
attr_reader :change_set
|
|
246
|
+
|
|
247
|
+
# @return [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] Error information if applicable
|
|
248
|
+
attr_reader :error
|
|
249
|
+
|
|
250
|
+
# @return [Boolean] Whether to revert to FDv1
|
|
251
|
+
attr_reader :revert_to_fdv1
|
|
252
|
+
|
|
253
|
+
# @return [String, nil] The environment ID if available
|
|
254
|
+
attr_reader :environment_id
|
|
255
|
+
|
|
256
|
+
#
|
|
257
|
+
# @param state [Symbol] The state of the data source
|
|
258
|
+
# @param change_set [ChangeSet, nil] The change set if available
|
|
259
|
+
# @param error [LaunchDarkly::Interfaces::DataSource::ErrorInfo, nil] Error information if applicable
|
|
260
|
+
# @param revert_to_fdv1 [Boolean] Whether to revert to FDv1
|
|
261
|
+
# @param environment_id [String, nil] The environment ID if available
|
|
262
|
+
#
|
|
263
|
+
def initialize(state:, change_set: nil, error: nil, revert_to_fdv1: false, environment_id: nil)
|
|
264
|
+
@state = state
|
|
265
|
+
@change_set = change_set
|
|
266
|
+
@error = error
|
|
267
|
+
@revert_to_fdv1 = revert_to_fdv1
|
|
268
|
+
@environment_id = environment_id
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
#
|
|
273
|
+
# Mixin that defines the required methods of a synchronizer implementation. A synchronizer
|
|
274
|
+
# is a component capable of synchronizing data from an external data source, such as a
|
|
275
|
+
# streaming or polling API.
|
|
276
|
+
#
|
|
277
|
+
# It is responsible for yielding Update objects that represent the current state of the
|
|
278
|
+
# data source, including any changes that have occurred since the last synchronization.
|
|
279
|
+
#
|
|
280
|
+
# Application code should not need to implement this directly; it is used internally by the SDK.
|
|
281
|
+
#
|
|
282
|
+
module Synchronizer
|
|
283
|
+
#
|
|
284
|
+
# Sync should begin the synchronization process for the data source, yielding
|
|
285
|
+
# Update objects until the connection is closed or an unrecoverable error
|
|
286
|
+
# occurs.
|
|
287
|
+
#
|
|
288
|
+
# @yield [Update] Yields Update objects as synchronization progresses
|
|
289
|
+
# @return [void]
|
|
290
|
+
#
|
|
291
|
+
def sync
|
|
292
|
+
raise NotImplementedError, "#{self.class} must implement #sync"
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
require "ldclient-rb/impl/model/serialization"
|
|
2
|
+
|
|
1
3
|
module LaunchDarkly
|
|
2
4
|
module Impl
|
|
3
5
|
class DependencyTracker
|
|
4
|
-
def initialize
|
|
6
|
+
def initialize(logger = nil)
|
|
5
7
|
@from = {}
|
|
6
8
|
@to = {}
|
|
9
|
+
@logger = logger
|
|
7
10
|
end
|
|
8
11
|
|
|
9
12
|
#
|
|
@@ -11,11 +14,11 @@ module LaunchDarkly
|
|
|
11
14
|
#
|
|
12
15
|
# @param from_kind [Object] the changed item's kind
|
|
13
16
|
# @param from_key [String] the changed item's key
|
|
14
|
-
# @param from_item [Object] the changed item
|
|
17
|
+
# @param from_item [Object] the changed item (can be a Hash, model object, or nil)
|
|
15
18
|
#
|
|
16
19
|
def update_dependencies_from(from_kind, from_key, from_item)
|
|
17
20
|
from_what = { kind: from_kind, key: from_key }
|
|
18
|
-
updated_dependencies = DependencyTracker.compute_dependencies_from(from_kind, from_item)
|
|
21
|
+
updated_dependencies = DependencyTracker.compute_dependencies_from(from_kind, from_item, @logger)
|
|
19
22
|
|
|
20
23
|
old_dependency_set = @from[from_what]
|
|
21
24
|
unless old_dependency_set.nil?
|
|
@@ -39,7 +42,7 @@ module LaunchDarkly
|
|
|
39
42
|
def self.segment_keys_from_clauses(clauses)
|
|
40
43
|
clauses.flat_map do |clause|
|
|
41
44
|
if clause.op == :segmentMatch
|
|
42
|
-
clause.values.map { |value| {kind:
|
|
45
|
+
clause.values.map { |value| {kind: DataStore::SEGMENTS, key: value }}
|
|
43
46
|
else
|
|
44
47
|
[]
|
|
45
48
|
end
|
|
@@ -48,19 +51,28 @@ module LaunchDarkly
|
|
|
48
51
|
|
|
49
52
|
#
|
|
50
53
|
# @param from_kind [String]
|
|
51
|
-
# @param from_item [LaunchDarkly::Impl::Model::FeatureFlag, LaunchDarkly::Impl::Model::Segment]
|
|
54
|
+
# @param from_item [Hash, LaunchDarkly::Impl::Model::FeatureFlag, LaunchDarkly::Impl::Model::Segment, nil] the item (can be a hash, model object, or nil)
|
|
55
|
+
# @param logger [Logger, nil] optional logger for deserialization
|
|
52
56
|
# @return [Set]
|
|
53
57
|
#
|
|
54
|
-
def self.compute_dependencies_from(from_kind, from_item)
|
|
55
|
-
|
|
58
|
+
def self.compute_dependencies_from(from_kind, from_item, logger = nil)
|
|
59
|
+
# Check for deleted items (matches Python: from_item.get('deleted', False))
|
|
60
|
+
return Set.new if from_item.nil? || (from_item.is_a?(Hash) && from_item[:deleted])
|
|
61
|
+
|
|
62
|
+
# Deserialize hash to model object if needed (matches Python: from_kind.decode(from_item) if isinstance(from_item, dict))
|
|
63
|
+
from_item = if from_item.is_a?(Hash)
|
|
64
|
+
LaunchDarkly::Impl::Model.deserialize(from_kind, from_item, logger)
|
|
65
|
+
else
|
|
66
|
+
from_item
|
|
67
|
+
end
|
|
56
68
|
|
|
57
|
-
if from_kind == LaunchDarkly::
|
|
69
|
+
if from_kind == DataStore::FEATURES && from_item.is_a?(LaunchDarkly::Impl::Model::FeatureFlag)
|
|
58
70
|
prereq_keys = from_item.prerequisites.map { |prereq| {kind: from_kind, key: prereq.key} }
|
|
59
71
|
segment_keys = from_item.rules.flat_map { |rule| DependencyTracker.segment_keys_from_clauses(rule.clauses) }
|
|
60
72
|
|
|
61
73
|
results = Set.new(prereq_keys)
|
|
62
74
|
results.merge(segment_keys)
|
|
63
|
-
elsif from_kind == LaunchDarkly::
|
|
75
|
+
elsif from_kind == DataStore::SEGMENTS && from_item.is_a?(LaunchDarkly::Impl::Model::Segment)
|
|
64
76
|
kind_and_keys = from_item.rules.flat_map do |rule|
|
|
65
77
|
DependencyTracker.segment_keys_from_clauses(rule.clauses)
|
|
66
78
|
end
|
|
@@ -4,6 +4,7 @@ require "ldclient-rb/impl/evaluator_helpers"
|
|
|
4
4
|
require "ldclient-rb/impl/evaluator_operators"
|
|
5
5
|
require "ldclient-rb/impl/model/feature_flag"
|
|
6
6
|
require "ldclient-rb/impl/model/segment"
|
|
7
|
+
require "ldclient-rb/impl/util"
|
|
7
8
|
|
|
8
9
|
module LaunchDarkly
|
|
9
10
|
module Impl
|
|
@@ -152,11 +153,11 @@ module LaunchDarkly
|
|
|
152
153
|
begin
|
|
153
154
|
detail = eval_internal(flag, context, result, state)
|
|
154
155
|
rescue EvaluationException => exn
|
|
155
|
-
|
|
156
|
+
Impl::Util.log_exception(@logger, "Unexpected error when evaluating flag #{flag.key}", exn)
|
|
156
157
|
result.detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(exn.error_kind))
|
|
157
158
|
return result, state
|
|
158
159
|
rescue => exn
|
|
159
|
-
|
|
160
|
+
Impl::Util.log_exception(@logger, "Unexpected error when evaluating flag #{flag.key}", exn)
|
|
160
161
|
result.detail = EvaluationDetail.new(nil, nil, EvaluationReason::error(EvaluationReason::ERROR_EXCEPTION))
|
|
161
162
|
return result, state
|
|
162
163
|
end
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
require "ldclient-rb/impl/unbounded_pool"
|
|
2
|
+
require "ldclient-rb/impl/util"
|
|
2
3
|
|
|
3
4
|
require "securerandom"
|
|
4
5
|
require "http"
|
|
@@ -21,7 +22,7 @@ module LaunchDarkly
|
|
|
21
22
|
@logger = config.logger
|
|
22
23
|
@retry_interval = retry_interval
|
|
23
24
|
@http_client_pool = UnboundedPool.new(
|
|
24
|
-
lambda {
|
|
25
|
+
lambda { Impl::Util.new_http_client(@config.events_uri, @config) },
|
|
25
26
|
lambda { |client| client.close })
|
|
26
27
|
end
|
|
27
28
|
|
|
@@ -81,9 +82,9 @@ module LaunchDarkly
|
|
|
81
82
|
end
|
|
82
83
|
return EventSenderResult.new(true, false, res_time)
|
|
83
84
|
end
|
|
84
|
-
must_shutdown = !
|
|
85
|
+
must_shutdown = !Impl::Util.http_error_recoverable?(status)
|
|
85
86
|
can_retry = !must_shutdown && attempt == 0
|
|
86
|
-
message =
|
|
87
|
+
message = Impl::Util.http_error_message(status, "event delivery", can_retry ? "will retry" : "some events were dropped")
|
|
87
88
|
@logger.error { "[LDClient] #{message}" }
|
|
88
89
|
if must_shutdown
|
|
89
90
|
return EventSenderResult.new(false, true, nil)
|