launchdarkly-server-sdk 8.11.2-java → 8.12.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ldclient-rb/config.rb +69 -9
  3. data/lib/ldclient-rb/context.rb +1 -1
  4. data/lib/ldclient-rb/data_system.rb +227 -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 +113 -0
  11. data/lib/ldclient-rb/impl/data_source/status_provider.rb +83 -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 +76 -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/data_source_builder_common.rb +77 -0
  21. data/lib/ldclient-rb/impl/data_system/fdv1.rb +20 -7
  22. data/lib/ldclient-rb/impl/data_system/fdv2.rb +472 -0
  23. data/lib/ldclient-rb/impl/data_system/http_config_options.rb +32 -0
  24. data/lib/ldclient-rb/impl/data_system/polling.rb +628 -0
  25. data/lib/ldclient-rb/impl/data_system/protocolv2.rb +264 -0
  26. data/lib/ldclient-rb/impl/data_system/streaming.rb +401 -0
  27. data/lib/ldclient-rb/impl/dependency_tracker.rb +21 -9
  28. data/lib/ldclient-rb/impl/evaluator.rb +3 -2
  29. data/lib/ldclient-rb/impl/event_sender.rb +14 -6
  30. data/lib/ldclient-rb/impl/expiring_cache.rb +79 -0
  31. data/lib/ldclient-rb/impl/integrations/file_data_source.rb +8 -8
  32. data/lib/ldclient-rb/impl/integrations/file_data_source_v2.rb +460 -0
  33. data/lib/ldclient-rb/impl/integrations/test_data/test_data_source_v2.rb +290 -0
  34. data/lib/ldclient-rb/impl/memoized_value.rb +34 -0
  35. data/lib/ldclient-rb/impl/migrations/migrator.rb +2 -1
  36. data/lib/ldclient-rb/impl/migrations/tracker.rb +2 -1
  37. data/lib/ldclient-rb/impl/model/serialization.rb +6 -6
  38. data/lib/ldclient-rb/impl/non_blocking_thread_pool.rb +48 -0
  39. data/lib/ldclient-rb/impl/repeating_task.rb +2 -2
  40. data/lib/ldclient-rb/impl/simple_lru_cache.rb +27 -0
  41. data/lib/ldclient-rb/impl/store_data_set_sorter.rb +1 -1
  42. data/lib/ldclient-rb/impl/util.rb +71 -0
  43. data/lib/ldclient-rb/impl.rb +1 -2
  44. data/lib/ldclient-rb/in_memory_store.rb +1 -18
  45. data/lib/ldclient-rb/integrations/file_data.rb +67 -0
  46. data/lib/ldclient-rb/integrations/test_data/flag_builder.rb +9 -9
  47. data/lib/ldclient-rb/integrations/test_data.rb +11 -11
  48. data/lib/ldclient-rb/integrations/test_data_v2/flag_builder_v2.rb +582 -0
  49. data/lib/ldclient-rb/integrations/test_data_v2.rb +254 -0
  50. data/lib/ldclient-rb/integrations/util/store_wrapper.rb +3 -2
  51. data/lib/ldclient-rb/interfaces/data_system.rb +704 -0
  52. data/lib/ldclient-rb/interfaces/feature_store.rb +5 -2
  53. data/lib/ldclient-rb/ldclient.rb +66 -132
  54. data/lib/ldclient-rb/util.rb +11 -70
  55. data/lib/ldclient-rb/version.rb +1 -1
  56. data/lib/ldclient-rb.rb +9 -17
  57. metadata +41 -19
  58. data/lib/ldclient-rb/cache_store.rb +0 -45
  59. data/lib/ldclient-rb/expiring_cache.rb +0 -77
  60. data/lib/ldclient-rb/memoized_value.rb +0 -32
  61. data/lib/ldclient-rb/non_blocking_thread_pool.rb +0 -46
  62. data/lib/ldclient-rb/polling.rb +0 -102
  63. data/lib/ldclient-rb/requestor.rb +0 -102
  64. data/lib/ldclient-rb/simple_lru_cache.rb +0 -25
  65. 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.to_sym]
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,76 @@
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
+ class StatusProviderV2
14
+ include LaunchDarkly::Interfaces::DataStore::StatusProvider
15
+
16
+ extend Forwardable
17
+ def_delegators :@status_broadcaster, :add_listener, :remove_listener
18
+
19
+ #
20
+ # Initialize the status provider.
21
+ #
22
+ # @param store [Object, nil] The feature store (may be nil for in-memory only)
23
+ # @param status_broadcaster [LaunchDarkly::Impl::Broadcaster] Broadcaster for status changes
24
+ #
25
+ def initialize(store, status_broadcaster)
26
+ @store = store
27
+ @status_broadcaster = status_broadcaster
28
+ @lock = Concurrent::ReadWriteLock.new
29
+ @status = LaunchDarkly::Interfaces::DataStore::Status.new(true, false)
30
+ @monitoring_enabled = store_supports_monitoring?
31
+ end
32
+
33
+ # (see LaunchDarkly::Interfaces::DataStore::UpdateSink#update_status)
34
+ def update_status(status)
35
+ modified = false
36
+
37
+ @lock.with_write_lock do
38
+ if @status.available != status.available || @status.stale != status.stale
39
+ @status = status
40
+ modified = true
41
+ end
42
+ end
43
+
44
+ @status_broadcaster.broadcast(status) if modified
45
+ end
46
+
47
+ # (see LaunchDarkly::Interfaces::DataStore::StatusProvider#status)
48
+ def status
49
+ @lock.with_read_lock do
50
+ LaunchDarkly::Interfaces::DataStore::Status.new(@status.available, @status.stale)
51
+ end
52
+ end
53
+
54
+ # (see LaunchDarkly::Interfaces::DataStore::StatusProvider#monitoring_enabled?)
55
+ def monitoring_enabled?
56
+ @monitoring_enabled
57
+ end
58
+
59
+ #
60
+ # Determines whether the store supports monitoring.
61
+ #
62
+ # @return [Boolean]
63
+ #
64
+ private def store_supports_monitoring?
65
+ return false if @store.nil?
66
+ return false unless @store.respond_to?(:monitoring_enabled?)
67
+
68
+ @store.monitoring_enabled?
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+
76
+