Skip to content

Commit 66b8876

Browse files
committed
chore: Create FDv1 datasystem implementation
1 parent 5b82f54 commit 66b8876

File tree

7 files changed

+603
-28
lines changed

7 files changed

+603
-28
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
require 'concurrent'
2+
3+
module LaunchDarkly
4+
module Impl
5+
module DataSource
6+
#
7+
# A minimal UpdateProcessor implementation used when the SDK is in offline mode
8+
# or daemon (LDD) mode. It does nothing except mark itself as initialized.
9+
#
10+
# @private
11+
#
12+
class NullUpdateProcessor
13+
include LaunchDarkly::Interfaces::DataSource
14+
15+
#
16+
# Creates a new NullUpdateProcessor.
17+
#
18+
def initialize
19+
@ready = Concurrent::Event.new
20+
end
21+
22+
#
23+
# Starts the data source. Since this is a null implementation, it immediately
24+
# sets the ready event to indicate initialization is complete.
25+
#
26+
# @return [Concurrent::Event] The ready event
27+
#
28+
def start
29+
@ready.set
30+
@ready
31+
end
32+
33+
#
34+
# Stops the data source. This is a no-op for the null implementation.
35+
#
36+
# @return [void]
37+
#
38+
def stop
39+
# Nothing to do
40+
end
41+
42+
#
43+
# Checks if the data source has been initialized.
44+
#
45+
# @return [Boolean] Always returns true since this is a null implementation
46+
#
47+
def initialized?
48+
true
49+
end
50+
end
51+
end
52+
end
53+
end

lib/ldclient-rb/impl/datasystem.rb

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@ module DataSystem
1919
#
2020
# Starts the data system.
2121
#
22-
# This method will return immediately. The provided event will be set when the system
22+
# This method will return immediately. The returned event will be set when the system
2323
# has reached an initial state (either permanently failed, e.g. due to bad auth, or succeeded).
2424
#
25-
# @param ready_event [Concurrent::Event] Event to set when initialization is complete
26-
# @return [void]
25+
# @return [Concurrent::Event] Event that will be set when initialization is complete
2726
#
28-
def start(ready_event)
27+
def start
2928
raise NotImplementedError, "#{self.class} must implement #start"
3029
end
3130

@@ -117,6 +116,17 @@ def set_flag_value_eval_fn(eval_fn)
117116
raise NotImplementedError, "#{self.class} must implement #set_flag_value_eval_fn"
118117
end
119118

119+
#
120+
# Sets the diagnostic accumulator for streaming initialization metrics.
121+
# This should be called before start() to ensure metrics are collected.
122+
#
123+
# @param diagnostic_accumulator [DiagnosticAccumulator] The diagnostic accumulator
124+
# @return [void]
125+
#
126+
def set_diagnostic_accumulator(diagnostic_accumulator)
127+
raise NotImplementedError, "#{self.class} must implement #set_diagnostic_accumulator"
128+
end
129+
120130
#
121131
# Represents the availability of data in the SDK.
122132
#
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
require 'concurrent'
2+
require 'ldclient-rb/impl/datasystem'
3+
require 'ldclient-rb/impl/data_source'
4+
require 'ldclient-rb/impl/data_store'
5+
require 'ldclient-rb/impl/datasource/null_processor'
6+
require 'ldclient-rb/impl/flag_tracker'
7+
require 'ldclient-rb/impl/broadcaster'
8+
9+
module LaunchDarkly
10+
module Impl
11+
module DataSystem
12+
#
13+
# FDv1 wires the existing v1 data source and store behavior behind the
14+
# generic DataSystem surface.
15+
#
16+
# @private
17+
#
18+
class FDv1
19+
include LaunchDarkly::Impl::DataSystem
20+
21+
#
22+
# Creates a new FDv1 data system.
23+
#
24+
# @param sdk_key [String] The SDK key
25+
# @param config [LaunchDarkly::Config] The SDK configuration
26+
#
27+
def initialize(sdk_key, config)
28+
@sdk_key = sdk_key
29+
@config = config
30+
@shared_executor = Concurrent::SingleThreadExecutor.new
31+
32+
# Set up data store plumbing
33+
@data_store_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @config.logger)
34+
@data_store_update_sink = LaunchDarkly::Impl::DataStore::UpdateSink.new(
35+
@data_store_broadcaster
36+
)
37+
38+
# Wrap the data store with client wrapper (must be created before status provider)
39+
@store_wrapper = LaunchDarkly::Impl::FeatureStoreClientWrapper.new(
40+
@config.feature_store,
41+
@data_store_update_sink,
42+
@config.logger
43+
)
44+
45+
# Create status provider with store wrapper
46+
@data_store_status_provider = LaunchDarkly::Impl::DataStore::StatusProvider.new(
47+
@store_wrapper,
48+
@data_store_update_sink
49+
)
50+
51+
# Set up data source plumbing
52+
@data_source_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @config.logger)
53+
@flag_change_broadcaster = LaunchDarkly::Impl::Broadcaster.new(@shared_executor, @config.logger)
54+
@flag_tracker_impl = LaunchDarkly::Impl::FlagTracker.new(
55+
@flag_change_broadcaster,
56+
lambda { |_key, _context| nil } # Replaced by client to use its evaluation method
57+
)
58+
@data_source_update_sink = LaunchDarkly::Impl::DataSource::UpdateSink.new(
59+
@store_wrapper,
60+
@data_source_broadcaster,
61+
@flag_change_broadcaster
62+
)
63+
@data_source_status_provider = LaunchDarkly::Impl::DataSource::StatusProvider.new(
64+
@data_source_broadcaster,
65+
@data_source_update_sink
66+
)
67+
68+
# Ensure v1 processors can find the sink via config for status updates
69+
@config.data_source_update_sink = @data_source_update_sink
70+
71+
# Update processor created in start(), because it needs the ready event
72+
@update_processor = nil
73+
74+
# Diagnostic accumulator provided by client for streaming metrics
75+
@diagnostic_accumulator = nil
76+
end
77+
78+
#
79+
# Starts the v1 update processor and returns immediately. The returned event
80+
# will be set by the processor upon first successful initialization or upon permanent failure.
81+
#
82+
# @return [Concurrent::Event] Event that will be set when initialization is complete
83+
#
84+
def start
85+
@update_processor = make_update_processor
86+
@update_processor.start
87+
end
88+
89+
#
90+
# Halts the data system, stopping the update processor and shutting down the executor.
91+
#
92+
# @return [void]
93+
#
94+
def stop
95+
@update_processor&.stop
96+
@shared_executor.shutdown
97+
end
98+
99+
#
100+
# Returns the feature store wrapper used by this data system.
101+
#
102+
# @return [LaunchDarkly::Impl::DataStore::ClientWrapper]
103+
#
104+
def store
105+
@store_wrapper
106+
end
107+
108+
#
109+
# Injects the flag value evaluation function used by the flag tracker to
110+
# compute FlagValueChange events. The function signature should be
111+
# (key, context) -> value.
112+
#
113+
# @param eval_fn [Proc] The evaluation function
114+
# @return [void]
115+
#
116+
def set_flag_value_eval_fn(eval_fn)
117+
@flag_tracker_impl = LaunchDarkly::Impl::FlagTracker.new(@flag_change_broadcaster, eval_fn)
118+
end
119+
120+
#
121+
# Sets the diagnostic accumulator for streaming initialization metrics.
122+
# This should be called before start() to ensure metrics are collected.
123+
#
124+
# @param diagnostic_accumulator [DiagnosticAccumulator] The diagnostic accumulator
125+
# @return [void]
126+
#
127+
def set_diagnostic_accumulator(diagnostic_accumulator)
128+
@diagnostic_accumulator = diagnostic_accumulator
129+
end
130+
131+
#
132+
# Returns the data source status provider.
133+
#
134+
# @return [LaunchDarkly::Interfaces::DataSource::StatusProvider]
135+
#
136+
def data_source_status_provider
137+
@data_source_status_provider
138+
end
139+
140+
#
141+
# Returns the data store status provider.
142+
#
143+
# @return [LaunchDarkly::Interfaces::DataStore::StatusProvider]
144+
#
145+
def data_store_status_provider
146+
@data_store_status_provider
147+
end
148+
149+
#
150+
# Returns the flag tracker.
151+
#
152+
# @return [LaunchDarkly::Interfaces::FlagTracker]
153+
#
154+
def flag_tracker
155+
@flag_tracker_impl
156+
end
157+
158+
#
159+
# Indicates what form of data is currently available.
160+
#
161+
# This is calculated dynamically based on current system state.
162+
#
163+
# @return [Symbol] One of DataAvailability constants
164+
#
165+
def data_availability
166+
if @config.offline?
167+
return DataAvailability::DEFAULTS
168+
end
169+
170+
if @update_processor && @update_processor.initialized?
171+
return DataAvailability::REFRESHED
172+
end
173+
174+
if @store_wrapper.initialized?
175+
return DataAvailability::CACHED
176+
end
177+
178+
DataAvailability::DEFAULTS
179+
end
180+
181+
#
182+
# Indicates the ideal form of data attainable given the current configuration.
183+
#
184+
# @return [Symbol] One of DataAvailability constants
185+
#
186+
def target_availability
187+
if @config.offline?
188+
return DataAvailability::DEFAULTS
189+
end
190+
# In LDD mode or normal connected modes, the ideal is to be refreshed
191+
DataAvailability::REFRESHED
192+
end
193+
194+
#
195+
# Creates the appropriate update processor based on the configuration.
196+
#
197+
# @return [Object] The update processor
198+
#
199+
private def make_update_processor
200+
# Handle custom data source (factory or instance)
201+
if @config.data_source
202+
return @config.data_source unless @config.data_source.respond_to?(:call)
203+
204+
# Factory - call with appropriate arity
205+
return @config.data_source.arity == 3 ?
206+
@config.data_source.call(@sdk_key, @config, @diagnostic_accumulator) :
207+
@config.data_source.call(@sdk_key, @config)
208+
end
209+
210+
# Create default data source based on config
211+
return LaunchDarkly::Impl::DataSource::NullUpdateProcessor.new if @config.offline? || @config.use_ldd?
212+
213+
if @config.stream?
214+
require 'ldclient-rb/stream'
215+
return LaunchDarkly::StreamProcessor.new(@sdk_key, @config, @diagnostic_accumulator)
216+
end
217+
218+
# Polling processor
219+
require 'ldclient-rb/polling'
220+
requestor = LaunchDarkly::Requestor.new(@sdk_key, @config)
221+
LaunchDarkly::PollingProcessor.new(@config, requestor)
222+
end
223+
end
224+
end
225+
end
226+
end
227+

lib/ldclient-rb/ldclient.rb

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
require "ldclient-rb/impl/broadcaster"
33
require "ldclient-rb/impl/data_source"
44
require "ldclient-rb/impl/data_store"
5+
require "ldclient-rb/impl/datasource/null_processor"
56
require "ldclient-rb/impl/diagnostic_events"
67
require "ldclient-rb/impl/evaluator"
78
require "ldclient-rb/impl/evaluation_with_hook_result"
@@ -132,7 +133,7 @@ def postfork(wait_for_sec = 5)
132133

133134
if @config.use_ldd?
134135
@config.logger.info { "[LDClient] Started LaunchDarkly Client in LDD mode" }
135-
@data_source = NullUpdateProcessor.new
136+
@data_source = LaunchDarkly::Impl::DataSource::NullUpdateProcessor.new
136137
return # requestor and update processor are not used in this mode
137138
end
138139

@@ -710,7 +711,7 @@ def close
710711

711712
def create_default_data_source(sdk_key, config, diagnostic_accumulator)
712713
if config.offline?
713-
return NullUpdateProcessor.new
714+
return LaunchDarkly::Impl::DataSource::NullUpdateProcessor.new
714715
end
715716
raise ArgumentError, "sdk_key must not be nil" if sdk_key.nil? # see LDClient constructor comment on sdk_key
716717
if config.stream?
@@ -877,23 +878,4 @@ def evaluate_internal(key, context, default, with_reasons)
877878
false
878879
end
879880
end
880-
881-
#
882-
# Used internally when the client is offline.
883-
# @private
884-
#
885-
class NullUpdateProcessor
886-
def start
887-
e = Concurrent::Event.new
888-
e.set
889-
e
890-
end
891-
892-
def initialized?
893-
true
894-
end
895-
896-
def stop
897-
end
898-
end
899881
end

0 commit comments

Comments
 (0)