44
55#include < boost/asio/post.hpp>
66
7+ #include < cassert>
78#include < chrono>
89#include < utility>
910#include < variant>
@@ -12,6 +13,7 @@ namespace launchdarkly::server_side::data_systems {
1213
1314namespace {
1415
16+ // Lets std::visit dispatch to a different lambda per variant alternative.
1517template <class ... Ts>
1618struct overloaded : Ts... {
1719 using Ts::operator ()...;
@@ -39,6 +41,7 @@ FDv2DataSystem::FDv2DataSystem(
3941 status_manager_(status_manager),
4042 store_(),
4143 change_notifier_(store_, store_),
44+ initialize_called_(false ),
4245 closed_(false ),
4346 selector_(),
4447 initializer_index_(0 ),
@@ -88,6 +91,9 @@ std::string const& FDv2DataSystem::Identity() const {
8891}
8992
9093void FDv2DataSystem::Initialize () {
94+ bool const already_called = initialize_called_.exchange (true );
95+ assert (!already_called && " Initialize() must be called at most once" );
96+
9197 LD_LOG (logger_, LogLevel::kInfo ) << Identity () << " : starting" ;
9298 if (initializer_factories_.empty () && synchronizer_factories_.empty ()) {
9399 // Offline mode: empty store is the canonical state.
@@ -98,8 +104,6 @@ void FDv2DataSystem::Initialize() {
98104}
99105
100106void FDv2DataSystem::RunNextInitializer () {
101- auto future = async::MakeFuture (data_interfaces::FDv2SourceResult{
102- data_interfaces::FDv2SourceResult::Shutdown{}});
103107 bool exhausted = false ;
104108 {
105109 std::lock_guard<std::mutex> lock (mutex_);
@@ -111,22 +115,21 @@ void FDv2DataSystem::RunNextInitializer() {
111115 } else {
112116 auto & factory = initializer_factories_[initializer_index_++];
113117 active_initializer_ = factory->Build ();
114- future = active_initializer_->Run ();
118+ active_initializer_->Run ().Then (
119+ [this ](data_interfaces::FDv2SourceResult const & result)
120+ -> std::monostate {
121+ OnInitializerResult (result);
122+ return {};
123+ },
124+ [ioc = ioc_](async::Continuation<void ()> work) {
125+ boost::asio::post (ioc, std::move (work));
126+ });
115127 }
116128 }
117129
118130 if (exhausted) {
119131 StartSynchronizers ();
120- return ;
121132 }
122-
123- std::move (future).Then (
124- [this ](
125- data_interfaces::FDv2SourceResult const & result) -> std::monostate {
126- OnInitializerResult (result);
127- return {};
128- },
129- async::kInlineExecutor );
130133}
131134
132135void FDv2DataSystem::OnInitializerResult (
@@ -164,13 +167,12 @@ void FDv2DataSystem::OnInitializerResult(
164167 te.error .Kind (), te.error .Message ());
165168 },
166169 [&](Result::Goodbye const &) {
167- LD_LOG (logger_, LogLevel::kWarn )
168- << Identity ()
169- << " : initializer received unexpected goodbye" ;
170+ LD_LOG (logger_, LogLevel::kDebug )
171+ << Identity () << " : ignoring goodbye from initializer" ;
170172 },
171173 [&](Result::Timeout const &) {
172- LD_LOG (logger_, LogLevel::kWarn )
173- << Identity () << " : initializer timed out (unexpected) " ;
174+ LD_LOG (logger_, LogLevel::kDebug )
175+ << Identity () << " : ignoring timeout from initializer " ;
174176 },
175177 },
176178 result.value );
@@ -192,47 +194,56 @@ void FDv2DataSystem::OnInitializerResult(
192194
193195void FDv2DataSystem::StartSynchronizers () {
194196 bool exhausted = false ;
197+ bool cycled_synchronizers = false ;
195198 {
196199 std::lock_guard<std::mutex> lock (mutex_);
197200 if (closed_) {
198201 return ;
199202 }
200203 if (synchronizer_index_ >= synchronizer_factories_.size ()) {
201204 exhausted = true ;
205+ cycled_synchronizers = synchronizer_index_ > 0 ;
202206 } else {
203207 auto & factory = synchronizer_factories_[synchronizer_index_++];
204208 active_synchronizer_ = factory->Build ();
205209 }
206210 }
207211
208212 if (exhausted) {
209- LD_LOG (logger_, LogLevel::kWarn )
210- << Identity () << " : no synchronizers available" ;
213+ // kOff when we can't continue updating; init-only with data stays
214+ // kValid.
215+ if (cycled_synchronizers || !store_.Initialized ()) {
216+ std::string const message =
217+ cycled_synchronizers
218+ ? " all data source acquisition methods have been exhausted"
219+ : " all initializers exhausted and no synchronizers "
220+ " configured" ;
221+ LD_LOG (logger_, LogLevel::kWarn ) << Identity () << " : " << message;
222+ status_manager_->SetState (
223+ DataSourceStatus::DataSourceState::kOff ,
224+ DataSourceStatus::ErrorInfo::ErrorKind::kUnknown , message);
225+ }
211226 return ;
212227 }
213228
214229 RunSynchronizerNext ();
215230}
216231
217232void FDv2DataSystem::RunSynchronizerNext () {
218- auto future = async::MakeFuture (data_interfaces::FDv2SourceResult{
219- data_interfaces::FDv2SourceResult::Shutdown{}});
220- {
221- std::lock_guard<std::mutex> lock (mutex_);
222- if (closed_ || !active_synchronizer_) {
223- return ;
224- }
225- future =
226- active_synchronizer_->Next (kSynchronizerNextTimeout , selector_);
233+ std::lock_guard<std::mutex> lock (mutex_);
234+ if (closed_ || !active_synchronizer_) {
235+ return ;
227236 }
228-
229- std::move (future).Then (
230- [this ](
231- data_interfaces::FDv2SourceResult const & result) -> std::monostate {
232- OnSynchronizerResult (result);
233- return {};
234- },
235- async::kInlineExecutor );
237+ active_synchronizer_->Next (kSynchronizerNextTimeout , selector_)
238+ .Then (
239+ [this ](data_interfaces::FDv2SourceResult const & result)
240+ -> std::monostate {
241+ OnSynchronizerResult (result);
242+ return {};
243+ },
244+ [ioc = ioc_](async::Continuation<void ()> work) {
245+ boost::asio::post (ioc, std::move (work));
246+ });
236247}
237248
238249void FDv2DataSystem::OnSynchronizerResult (
@@ -266,10 +277,11 @@ void FDv2DataSystem::OnSynchronizerResult(
266277 advance = true ;
267278 },
268279 [&](Result::Goodbye const & gb) {
269- LD_LOG (logger_, LogLevel::kInfo )
270- << Identity () << " : synchronizer goodbye"
280+ // The synchronizer handles goodbye internally (reconnects);
281+ // the orchestrator just loops on the same source.
282+ LD_LOG (logger_, LogLevel::kDebug )
283+ << Identity () << " : ignoring goodbye from synchronizer"
271284 << (gb.reason ? " : " + *gb.reason : " " );
272- advance = true ;
273285 },
274286 [&](Result::Timeout const &) {
275287 LD_LOG (logger_, LogLevel::kDebug )
0 commit comments