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,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
require "ldclient-rb/interfaces"
|
|
5
|
+
require "ldclient-rb/impl/store_data_set_sorter"
|
|
6
|
+
require "ldclient-rb/impl/repeating_task"
|
|
7
|
+
|
|
8
|
+
module LaunchDarkly
|
|
9
|
+
module Impl
|
|
10
|
+
module DataStore
|
|
11
|
+
#
|
|
12
|
+
# Provides additional behavior that the client requires before or after feature store operations.
|
|
13
|
+
# Currently this just means sorting the data set for init() and dealing with data store status listeners.
|
|
14
|
+
#
|
|
15
|
+
class FeatureStoreClientWrapperV2
|
|
16
|
+
include LaunchDarkly::Interfaces::FeatureStore
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# Initialize the wrapper.
|
|
20
|
+
#
|
|
21
|
+
# @param store [LaunchDarkly::Interfaces::FeatureStore] The underlying feature store
|
|
22
|
+
# @param store_update_sink [LaunchDarkly::Impl::DataStore::StatusProviderV2] The status provider for updates
|
|
23
|
+
# @param logger [Logger] The logger instance
|
|
24
|
+
#
|
|
25
|
+
def initialize(store, store_update_sink, logger)
|
|
26
|
+
@store = store
|
|
27
|
+
@store_update_sink = store_update_sink
|
|
28
|
+
@logger = logger
|
|
29
|
+
@monitoring_enabled = store_supports_monitoring?
|
|
30
|
+
|
|
31
|
+
# Thread synchronization
|
|
32
|
+
@mutex = Mutex.new
|
|
33
|
+
@last_available = true
|
|
34
|
+
@poller = nil
|
|
35
|
+
@closed = false
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# (see LaunchDarkly::Interfaces::FeatureStore#init)
|
|
39
|
+
def init(all_data)
|
|
40
|
+
wrapper { @store.init(FeatureStoreDataSetSorter.sort_all_collections(all_data)) }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# (see LaunchDarkly::Interfaces::FeatureStore#get)
|
|
44
|
+
def get(kind, key)
|
|
45
|
+
wrapper { @store.get(kind, key) }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# (see LaunchDarkly::Interfaces::FeatureStore#all)
|
|
49
|
+
def all(kind)
|
|
50
|
+
wrapper { @store.all(kind) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# (see LaunchDarkly::Interfaces::FeatureStore#delete)
|
|
54
|
+
def delete(kind, key, version)
|
|
55
|
+
wrapper { @store.delete(kind, key, version) }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# (see LaunchDarkly::Interfaces::FeatureStore#upsert)
|
|
59
|
+
def upsert(kind, item)
|
|
60
|
+
wrapper { @store.upsert(kind, item) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# (see LaunchDarkly::Interfaces::FeatureStore#initialized?)
|
|
64
|
+
def initialized?
|
|
65
|
+
@store.initialized?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# (see LaunchDarkly::Interfaces::FeatureStore#stop)
|
|
69
|
+
def stop
|
|
70
|
+
poller_to_stop = nil
|
|
71
|
+
|
|
72
|
+
@mutex.synchronize do
|
|
73
|
+
return if @closed
|
|
74
|
+
|
|
75
|
+
@closed = true
|
|
76
|
+
poller_to_stop = @poller
|
|
77
|
+
@poller = nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
poller_to_stop.stop if poller_to_stop
|
|
81
|
+
|
|
82
|
+
@store.stop
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# Returns whether monitoring is enabled.
|
|
87
|
+
#
|
|
88
|
+
# @return [Boolean]
|
|
89
|
+
#
|
|
90
|
+
def monitoring_enabled?
|
|
91
|
+
@monitoring_enabled
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
#
|
|
95
|
+
# Wraps store operations with exception handling and availability tracking.
|
|
96
|
+
#
|
|
97
|
+
# @yield The block to execute
|
|
98
|
+
# @return [Object] The result of the block
|
|
99
|
+
#
|
|
100
|
+
private def wrapper
|
|
101
|
+
begin
|
|
102
|
+
yield
|
|
103
|
+
rescue StandardError
|
|
104
|
+
update_availability(false) if @monitoring_enabled
|
|
105
|
+
raise
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
#
|
|
110
|
+
# Updates the availability status of the store.
|
|
111
|
+
#
|
|
112
|
+
# @param available [Boolean] Whether the store is available
|
|
113
|
+
# @return [void]
|
|
114
|
+
#
|
|
115
|
+
private def update_availability(available)
|
|
116
|
+
state_changed = false
|
|
117
|
+
poller_to_stop = nil
|
|
118
|
+
|
|
119
|
+
@mutex.synchronize do
|
|
120
|
+
return if @closed
|
|
121
|
+
return if available == @last_available
|
|
122
|
+
|
|
123
|
+
state_changed = true
|
|
124
|
+
@last_available = available
|
|
125
|
+
|
|
126
|
+
if available
|
|
127
|
+
poller_to_stop = @poller
|
|
128
|
+
@poller = nil
|
|
129
|
+
elsif @poller.nil?
|
|
130
|
+
task = LaunchDarkly::Impl::RepeatingTask.new(0.5, 0, method(:check_availability), @logger, "LDClient/DataStoreWrapperV2#check-availability")
|
|
131
|
+
@poller = task
|
|
132
|
+
@poller.start
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
return unless state_changed
|
|
137
|
+
|
|
138
|
+
if available
|
|
139
|
+
@logger.warn { "[LDClient] Persistent store is available again" }
|
|
140
|
+
else
|
|
141
|
+
@logger.warn { "[LDClient] Detected persistent store unavailability; updates will be cached until it recovers" }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
status = LaunchDarkly::Interfaces::DataStore::Status.new(available, true)
|
|
145
|
+
@store_update_sink.update_status(status)
|
|
146
|
+
|
|
147
|
+
poller_to_stop.stop if poller_to_stop
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
#
|
|
151
|
+
# Checks if the store is available.
|
|
152
|
+
#
|
|
153
|
+
# @return [void]
|
|
154
|
+
#
|
|
155
|
+
private def check_availability
|
|
156
|
+
begin
|
|
157
|
+
update_availability(true) if @store.available?
|
|
158
|
+
rescue => e
|
|
159
|
+
@logger.error { "[LDClient] Unexpected error from data store status function: #{e.message}" }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
#
|
|
164
|
+
# Determines whether the wrapped store can support enabling monitoring.
|
|
165
|
+
#
|
|
166
|
+
# The wrapped store must provide a monitoring_enabled? method, which must
|
|
167
|
+
# be true. But this alone is not sufficient.
|
|
168
|
+
#
|
|
169
|
+
# Because this class wraps all interactions with a provided store, it can
|
|
170
|
+
# technically "monitor" any store. However, monitoring also requires that
|
|
171
|
+
# we notify listeners when the store is available again.
|
|
172
|
+
#
|
|
173
|
+
# We determine this by checking the store's available? method, so this
|
|
174
|
+
# is also a requirement for monitoring support.
|
|
175
|
+
#
|
|
176
|
+
# @return [Boolean]
|
|
177
|
+
#
|
|
178
|
+
private def store_supports_monitoring?
|
|
179
|
+
return false unless @store.respond_to?(:monitoring_enabled?)
|
|
180
|
+
return false unless @store.respond_to?(:available?)
|
|
181
|
+
|
|
182
|
+
@store.monitoring_enabled?
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
require "concurrent/atomics"
|
|
5
|
+
require "ldclient-rb/impl/data_store"
|
|
6
|
+
require "ldclient-rb/interfaces/data_system"
|
|
7
|
+
|
|
8
|
+
module LaunchDarkly
|
|
9
|
+
module Impl
|
|
10
|
+
module DataStore
|
|
11
|
+
#
|
|
12
|
+
# InMemoryFeatureStoreV2 is a read-only in-memory store implementation for FDv2.
|
|
13
|
+
#
|
|
14
|
+
class InMemoryFeatureStoreV2
|
|
15
|
+
include LaunchDarkly::Interfaces::DataSystem::ReadOnlyStore
|
|
16
|
+
def initialize(logger)
|
|
17
|
+
@logger = logger
|
|
18
|
+
@lock = Concurrent::ReadWriteLock.new
|
|
19
|
+
@initialized = Concurrent::AtomicBoolean.new(false)
|
|
20
|
+
@items = {}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
#
|
|
24
|
+
# (see LaunchDarkly::Interfaces::DataSystem::ReadOnlyStore#get)
|
|
25
|
+
#
|
|
26
|
+
def get(kind, key)
|
|
27
|
+
@lock.with_read_lock do
|
|
28
|
+
items_of_kind = @items[kind]
|
|
29
|
+
return nil if items_of_kind.nil?
|
|
30
|
+
|
|
31
|
+
item = items_of_kind[key]
|
|
32
|
+
return nil if item.nil?
|
|
33
|
+
return nil if item[:deleted]
|
|
34
|
+
|
|
35
|
+
item
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
#
|
|
40
|
+
# (see LaunchDarkly::Interfaces::DataSystem::ReadOnlyStore#all)
|
|
41
|
+
#
|
|
42
|
+
def all(kind)
|
|
43
|
+
@lock.with_read_lock do
|
|
44
|
+
items_of_kind = @items[kind]
|
|
45
|
+
return {} if items_of_kind.nil?
|
|
46
|
+
|
|
47
|
+
items_of_kind.select { |_k, item| !item[:deleted] }
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
#
|
|
52
|
+
# (see LaunchDarkly::Interfaces::DataSystem::ReadOnlyStore#initialized?)
|
|
53
|
+
#
|
|
54
|
+
def initialized?
|
|
55
|
+
@initialized.value
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# Initializes the store with a full set of data, replacing any existing data.
|
|
60
|
+
#
|
|
61
|
+
# @param collections [Hash<LaunchDarkly::Impl::DataStore::DataKind, Hash<String, Hash>>] Hash of data kinds to collections of items
|
|
62
|
+
# @return [Boolean] true if successful, false otherwise
|
|
63
|
+
#
|
|
64
|
+
def set_basis(collections)
|
|
65
|
+
all_decoded = decode_collection(collections)
|
|
66
|
+
return false if all_decoded.nil?
|
|
67
|
+
|
|
68
|
+
@lock.with_write_lock do
|
|
69
|
+
@items.clear
|
|
70
|
+
@items.update(all_decoded)
|
|
71
|
+
@initialized.make_true
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
true
|
|
75
|
+
rescue => e
|
|
76
|
+
@logger.error { "[LDClient] Failed applying set_basis: #{e.message}" }
|
|
77
|
+
false
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
#
|
|
81
|
+
# Applies a delta update to the store.
|
|
82
|
+
#
|
|
83
|
+
# @param collections [Hash<LaunchDarkly::Impl::DataStore::DataKind, Hash<String, Hash>>] Hash of data kinds to collections with updates
|
|
84
|
+
# @return [Boolean] true if successful, false otherwise
|
|
85
|
+
#
|
|
86
|
+
def apply_delta(collections)
|
|
87
|
+
all_decoded = decode_collection(collections)
|
|
88
|
+
return false if all_decoded.nil?
|
|
89
|
+
|
|
90
|
+
@lock.with_write_lock do
|
|
91
|
+
all_decoded.each do |kind, kind_data|
|
|
92
|
+
items_of_kind = @items[kind] ||= {}
|
|
93
|
+
kind_data.each do |key, item|
|
|
94
|
+
items_of_kind[key] = item
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
true
|
|
100
|
+
rescue => e
|
|
101
|
+
@logger.error { "[LDClient] Failed applying apply_delta: #{e.message}" }
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
#
|
|
106
|
+
# Decodes a collection of items.
|
|
107
|
+
#
|
|
108
|
+
# @param collections [Hash<LaunchDarkly::Impl::DataStore::DataKind, Hash<String, Hash>>] Hash of data kinds to collections
|
|
109
|
+
# @return [Hash<LaunchDarkly::Impl::DataStore::DataKind, Hash<Symbol, Hash>>, nil] Decoded collection with symbol keys, or nil on error
|
|
110
|
+
#
|
|
111
|
+
private def decode_collection(collections)
|
|
112
|
+
all_decoded = {}
|
|
113
|
+
|
|
114
|
+
collections.each do |kind, collection|
|
|
115
|
+
items_decoded = {}
|
|
116
|
+
collection.each do |key, item|
|
|
117
|
+
items_decoded[key] = LaunchDarkly::Impl::Model.deserialize(kind, item, @logger)
|
|
118
|
+
end
|
|
119
|
+
all_decoded[kind] = items_decoded
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
all_decoded
|
|
123
|
+
rescue => e
|
|
124
|
+
@logger.error { "[LDClient] Failed decoding collection: #{e.message}" }
|
|
125
|
+
nil
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent"
|
|
4
|
+
require "forwardable"
|
|
5
|
+
require "ldclient-rb/interfaces"
|
|
6
|
+
|
|
7
|
+
module LaunchDarkly
|
|
8
|
+
module Impl
|
|
9
|
+
module DataStore
|
|
10
|
+
#
|
|
11
|
+
# StatusProviderV2 is the FDv2-specific implementation of {LaunchDarkly::Interfaces::DataStore::StatusProvider}.
|
|
12
|
+
#
|
|
13
|
+
# This type is not stable, and not subject to any backwards
|
|
14
|
+
# compatibility guarantees or semantic versioning. It is not suitable for production usage.
|
|
15
|
+
#
|
|
16
|
+
# Do not use it.
|
|
17
|
+
# You have been warned.
|
|
18
|
+
#
|
|
19
|
+
class StatusProviderV2
|
|
20
|
+
include LaunchDarkly::Interfaces::DataStore::StatusProvider
|
|
21
|
+
|
|
22
|
+
extend Forwardable
|
|
23
|
+
def_delegators :@status_broadcaster, :add_listener, :remove_listener
|
|
24
|
+
|
|
25
|
+
#
|
|
26
|
+
# Initialize the status provider.
|
|
27
|
+
#
|
|
28
|
+
# @param store [Object, nil] The feature store (may be nil for in-memory only)
|
|
29
|
+
# @param status_broadcaster [LaunchDarkly::Impl::Broadcaster] Broadcaster for status changes
|
|
30
|
+
#
|
|
31
|
+
def initialize(store, status_broadcaster)
|
|
32
|
+
@store = store
|
|
33
|
+
@status_broadcaster = status_broadcaster
|
|
34
|
+
@lock = Concurrent::ReadWriteLock.new
|
|
35
|
+
@status = LaunchDarkly::Interfaces::DataStore::Status.new(true, false)
|
|
36
|
+
@monitoring_enabled = store_supports_monitoring?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# (see LaunchDarkly::Interfaces::DataStore::UpdateSink#update_status)
|
|
40
|
+
def update_status(status)
|
|
41
|
+
modified = false
|
|
42
|
+
|
|
43
|
+
@lock.with_write_lock do
|
|
44
|
+
if @status.available != status.available || @status.stale != status.stale
|
|
45
|
+
@status = status
|
|
46
|
+
modified = true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
@status_broadcaster.broadcast(status) if modified
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# (see LaunchDarkly::Interfaces::DataStore::StatusProvider#status)
|
|
54
|
+
def status
|
|
55
|
+
@lock.with_read_lock do
|
|
56
|
+
LaunchDarkly::Interfaces::DataStore::Status.new(@status.available, @status.stale)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# (see LaunchDarkly::Interfaces::DataStore::StatusProvider#monitoring_enabled?)
|
|
61
|
+
def monitoring_enabled?
|
|
62
|
+
@monitoring_enabled
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
#
|
|
66
|
+
# Determines whether the store supports monitoring.
|
|
67
|
+
#
|
|
68
|
+
# @return [Boolean]
|
|
69
|
+
#
|
|
70
|
+
private def store_supports_monitoring?
|
|
71
|
+
return false if @store.nil?
|
|
72
|
+
return false unless @store.respond_to?(:monitoring_enabled?)
|
|
73
|
+
|
|
74
|
+
@store.monitoring_enabled?
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|