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,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
|
|
@@ -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)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
|
|
2
|
+
module LaunchDarkly
|
|
3
|
+
module Impl
|
|
4
|
+
# A thread-safe cache with maximum number of entries and TTL.
|
|
5
|
+
# Adapted from https://github.com/SamSaffron/lru_redux/blob/master/lib/lru_redux/ttl/cache.rb
|
|
6
|
+
# under MIT license with the following changes:
|
|
7
|
+
# * made thread-safe
|
|
8
|
+
# * removed many unused methods
|
|
9
|
+
# * reading a key does not reset its expiration time, only writing
|
|
10
|
+
class ExpiringCache
|
|
11
|
+
def initialize(max_size, ttl)
|
|
12
|
+
@max_size = max_size
|
|
13
|
+
@ttl = ttl
|
|
14
|
+
@data_lru = {}
|
|
15
|
+
@data_ttl = {}
|
|
16
|
+
@lock = Mutex.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def [](key)
|
|
20
|
+
@lock.synchronize do
|
|
21
|
+
ttl_evict
|
|
22
|
+
@data_lru[key]
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def []=(key, val)
|
|
27
|
+
@lock.synchronize do
|
|
28
|
+
ttl_evict
|
|
29
|
+
|
|
30
|
+
@data_lru.delete(key)
|
|
31
|
+
@data_ttl.delete(key)
|
|
32
|
+
|
|
33
|
+
@data_lru[key] = val
|
|
34
|
+
@data_ttl[key] = Time.now.to_f
|
|
35
|
+
|
|
36
|
+
if @data_lru.size > @max_size
|
|
37
|
+
key, _ = @data_lru.first # hashes have a FIFO ordering in Ruby
|
|
38
|
+
|
|
39
|
+
@data_ttl.delete(key)
|
|
40
|
+
@data_lru.delete(key)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
val
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def delete(key)
|
|
48
|
+
@lock.synchronize do
|
|
49
|
+
ttl_evict
|
|
50
|
+
|
|
51
|
+
@data_lru.delete(key)
|
|
52
|
+
@data_ttl.delete(key)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def clear
|
|
57
|
+
@lock.synchronize do
|
|
58
|
+
@data_lru.clear
|
|
59
|
+
@data_ttl.clear
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def ttl_evict
|
|
66
|
+
ttl_horizon = Time.now.to_f - @ttl
|
|
67
|
+
key, time = @data_ttl.first
|
|
68
|
+
|
|
69
|
+
until time.nil? || time > ttl_horizon
|
|
70
|
+
@data_ttl.delete(key)
|
|
71
|
+
@data_lru.delete(key)
|
|
72
|
+
|
|
73
|
+
key, time = @data_ttl.first
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
require 'ldclient-rb/in_memory_store'
|
|
2
|
-
require 'ldclient-rb/util'
|
|
2
|
+
require 'ldclient-rb/impl/util'
|
|
3
3
|
|
|
4
4
|
require 'concurrent/atomics'
|
|
5
5
|
require 'json'
|
|
@@ -75,14 +75,14 @@ module LaunchDarkly
|
|
|
75
75
|
|
|
76
76
|
def load_all
|
|
77
77
|
all_data = {
|
|
78
|
-
FEATURES => {},
|
|
79
|
-
SEGMENTS => {},
|
|
78
|
+
Impl::DataStore::FEATURES => {},
|
|
79
|
+
Impl::DataStore::SEGMENTS => {},
|
|
80
80
|
}
|
|
81
81
|
@paths.each do |path|
|
|
82
82
|
begin
|
|
83
83
|
load_file(path, all_data)
|
|
84
84
|
rescue => exn
|
|
85
|
-
|
|
85
|
+
Impl::Util.log_exception(@logger, "Unable to load flag data from \"#{path}\"", exn)
|
|
86
86
|
@data_source_update_sink&.update_status(
|
|
87
87
|
LaunchDarkly::Interfaces::DataSource::Status::INTERRUPTED,
|
|
88
88
|
LaunchDarkly::Interfaces::DataSource::ErrorInfo.new(LaunchDarkly::Interfaces::DataSource::ErrorInfo::INVALID_DATA, 0, exn.to_s, Time.now)
|
|
@@ -105,14 +105,14 @@ module LaunchDarkly
|
|
|
105
105
|
parsed = parse_content(File.read(path))
|
|
106
106
|
(parsed[:flags] || {}).each do |key, flag|
|
|
107
107
|
flag[:version] = version
|
|
108
|
-
add_item(all_data, FEATURES, flag)
|
|
108
|
+
add_item(all_data, Impl::DataStore::FEATURES, flag)
|
|
109
109
|
end
|
|
110
110
|
(parsed[:flagValues] || {}).each do |key, value|
|
|
111
|
-
add_item(all_data, FEATURES, make_flag_with_value(key.to_s, value, version))
|
|
111
|
+
add_item(all_data, Impl::DataStore::FEATURES, make_flag_with_value(key.to_s, value, version))
|
|
112
112
|
end
|
|
113
113
|
(parsed[:segments] || {}).each do |key, segment|
|
|
114
114
|
segment[:version] = version
|
|
115
|
-
add_item(all_data, SEGMENTS, segment)
|
|
115
|
+
add_item(all_data, Impl::DataStore::SEGMENTS, segment)
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
|
|
@@ -212,7 +212,7 @@ module LaunchDarkly
|
|
|
212
212
|
end
|
|
213
213
|
reloader.call if changed
|
|
214
214
|
rescue => exn
|
|
215
|
-
|
|
215
|
+
Impl::Util.log_exception(logger, "Unexpected exception in FileDataSourcePoller", exn)
|
|
216
216
|
end
|
|
217
217
|
end
|
|
218
218
|
end
|