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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ldclient-rb/config.rb +66 -3
  3. data/lib/ldclient-rb/context.rb +1 -1
  4. data/lib/ldclient-rb/data_system.rb +243 -0
  5. data/lib/ldclient-rb/events.rb +34 -19
  6. data/lib/ldclient-rb/flags_state.rb +1 -1
  7. data/lib/ldclient-rb/impl/big_segments.rb +4 -4
  8. data/lib/ldclient-rb/impl/cache_store.rb +44 -0
  9. data/lib/ldclient-rb/impl/data_source/polling.rb +108 -0
  10. data/lib/ldclient-rb/impl/data_source/requestor.rb +106 -0
  11. data/lib/ldclient-rb/impl/data_source/status_provider.rb +78 -0
  12. data/lib/ldclient-rb/impl/data_source/stream.rb +198 -0
  13. data/lib/ldclient-rb/impl/data_source.rb +3 -3
  14. data/lib/ldclient-rb/impl/data_store/data_kind.rb +108 -0
  15. data/lib/ldclient-rb/impl/data_store/feature_store_client_wrapper.rb +187 -0
  16. data/lib/ldclient-rb/impl/data_store/in_memory_feature_store.rb +130 -0
  17. data/lib/ldclient-rb/impl/data_store/status_provider.rb +82 -0
  18. data/lib/ldclient-rb/impl/data_store/store.rb +371 -0
  19. data/lib/ldclient-rb/impl/data_store.rb +11 -97
  20. data/lib/ldclient-rb/impl/data_system/fdv1.rb +20 -7
  21. data/lib/ldclient-rb/impl/data_system/fdv2.rb +471 -0
  22. data/lib/ldclient-rb/impl/data_system/polling.rb +601 -0
  23. data/lib/ldclient-rb/impl/data_system/protocolv2.rb +264 -0
  24. data/lib/ldclient-rb/impl/dependency_tracker.rb +21 -9
  25. data/lib/ldclient-rb/impl/evaluator.rb +3 -2
  26. data/lib/ldclient-rb/impl/event_sender.rb +4 -3
  27. data/lib/ldclient-rb/impl/expiring_cache.rb +79 -0
  28. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
  29. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +288 -0
  30. data/lib/ldclient-rb/impl/memoized_value.rb +34 -0
  31. data/lib/ldclient-rb/impl/migrations/migrator.rb +2 -1
  32. data/lib/ldclient-rb/impl/migrations/tracker.rb +2 -1
  33. data/lib/ldclient-rb/impl/model/serialization.rb +6 -6
  34. data/lib/ldclient-rb/impl/non_blocking_thread_pool.rb +48 -0
  35. data/lib/ldclient-rb/impl/repeating_task.rb +2 -2
  36. data/lib/ldclient-rb/impl/simple_lru_cache.rb +27 -0
  37. data/lib/ldclient-rb/impl/util.rb +65 -0
  38. data/lib/ldclient-rb/impl.rb +1 -2
  39. data/lib/ldclient-rb/in_memory_store.rb +1 -18
  40. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +9 -9
  41. data/lib/ldclient-rb/integrations/test_data.rb +11 -11
  42. data/lib/ldclient-rb/integrations/test_data_v2/flag_builder_v2.rb +582 -0
  43. data/lib/ldclient-rb/integrations/test_data_v2.rb +248 -0
  44. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +3 -2
  45. data/lib/ldclient-rb/interfaces/data_system.rb +755 -0
  46. data/lib/ldclient-rb/interfaces/feature_store.rb +3 -0
  47. data/lib/ldclient-rb/ldclient.rb +55 -131
  48. data/lib/ldclient-rb/util.rb +11 -70
  49. data/lib/ldclient-rb/version.rb +1 -1
  50. data/lib/ldclient-rb.rb +8 -17
  51. metadata +35 -17
  52. data/lib/ldclient-rb/cache_store.rb +0 -45
  53. data/lib/ldclient-rb/expiring_cache.rb +0 -77
  54. data/lib/ldclient-rb/memoized_value.rb +0 -32
  55. data/lib/ldclient-rb/non_blocking_thread_pool.rb +0 -46
  56. data/lib/ldclient-rb/polling.rb +0 -102
  57. data/lib/ldclient-rb/requestor.rb +0 -102
  58. data/lib/ldclient-rb/simple_lru_cache.rb +0 -25
  59. 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
+