Skip to content

Commit dab36f4

Browse files
committed
merge: pick up FDv1 source lifecycle fix from #540
2 parents 7afaf7e + 27f5c1e commit dab36f4

2 files changed

Lines changed: 35 additions & 36 deletions

File tree

libs/server-sdk/src/data_systems/fdv2/fdv1_adapter_synchronizer.cpp

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,9 @@ using DataSourceState = DataSourceStatus::DataSourceState;
99

1010
// ----- State -----
1111

12-
bool FDv1AdapterSynchronizer::State::TryStart() {
13-
std::lock_guard lock(mutex_);
14-
if (started_ || closed_) {
15-
return false;
16-
}
17-
started_ = true;
18-
return true;
19-
}
20-
21-
bool FDv1AdapterSynchronizer::State::MarkClosed() {
22-
std::lock_guard lock(mutex_);
23-
closed_ = true;
24-
return started_;
25-
}
12+
FDv1AdapterSynchronizer::State::State(
13+
async::Future<std::monostate> closed_future)
14+
: closed_future_(std::move(closed_future)) {}
2615

2716
async::Future<FDv2SourceResult> FDv1AdapterSynchronizer::State::GetNext() {
2817
std::lock_guard lock(mutex_);
@@ -52,7 +41,7 @@ void FDv1AdapterSynchronizer::State::Notify(FDv2SourceResult result) {
5241
std::optional<async::Promise<FDv2SourceResult>> promise;
5342
{
5443
std::lock_guard lock(mutex_);
55-
if (closed_) {
44+
if (closed_future_.IsFinished()) {
5645
return;
5746
}
5847
if (pending_promise_) {
@@ -135,7 +124,7 @@ std::string const& FDv1AdapterSynchronizer::ConvertingDestination::Identity()
135124
FDv1AdapterSynchronizer::FDv1AdapterSynchronizer(
136125
std::unique_ptr<data_interfaces::IDataSynchronizer> fdv1_source,
137126
data_components::DataSourceStatusManager* status_manager)
138-
: state_(std::make_shared<State>()),
127+
: state_(std::make_shared<State>(close_promise_.GetFuture())),
139128
destination_(std::make_unique<ConvertingDestination>(state_)),
140129
status_manager_(status_manager),
141130
status_subscription_(status_manager_->OnDataSourceStatusChange(
@@ -172,9 +161,13 @@ async::Future<FDv2SourceResult> FDv1AdapterSynchronizer::Next(
172161
return async::MakeFuture(
173162
FDv2SourceResult{FDv2SourceResult::Shutdown{}});
174163
}
175-
if (state_->TryStart()) {
176-
fdv1_source_->StartAsync(destination_.get(),
177-
/*bootstrap_data=*/nullptr);
164+
{
165+
std::lock_guard lock(lifecycle_mutex_);
166+
if (!started_) {
167+
started_ = true;
168+
fdv1_source_->StartAsync(destination_.get(),
169+
/*bootstrap_data=*/nullptr);
170+
}
178171
}
179172
auto result_future = state_->GetNext();
180173
if (result_future.IsFinished()) {
@@ -198,7 +191,10 @@ void FDv1AdapterSynchronizer::Close() {
198191
if (!close_promise_.Resolve(std::monostate{})) {
199192
return;
200193
}
201-
if (state_->MarkClosed()) {
194+
std::lock_guard lock(lifecycle_mutex_);
195+
bool const was_started = started_;
196+
started_ = true;
197+
if (was_started) {
202198
fdv1_source_->ShutdownAsync([] {});
203199
}
204200
}

libs/server-sdk/src/data_systems/fdv2/fdv1_adapter_synchronizer.hpp

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -55,21 +55,13 @@ class FDv1AdapterSynchronizer final
5555

5656
private:
5757
/**
58-
* Holds the lifecycle, result queue, and pending Next() promise; shared
59-
* with the FDv1 source's IDestination via the inner ConvertingDestination.
58+
* Holds the result queue and pending Next() promise; shared with the
59+
* FDv1 source's IDestination via the inner ConvertingDestination.
6060
* All methods are thread-safe.
6161
*/
6262
class State {
6363
public:
64-
// Returns true if this call transitioned Initial → Started; false if
65-
// already started or already closed. Used to gate the one-time
66-
// StartAsync call on the wrapped FDv1 source.
67-
bool TryStart();
68-
69-
// Marks the state closed and returns whether the source was started
70-
// before the transition (so the caller knows whether ShutdownAsync
71-
// needs to be called).
72-
bool MarkClosed();
64+
explicit State(async::Future<std::monostate> closed_future);
7365

7466
async::Future<data_interfaces::FDv2SourceResult> GetNext();
7567

@@ -81,10 +73,12 @@ class FDv1AdapterSynchronizer final
8173
void Notify(data_interfaces::FDv2SourceResult result);
8274

8375
private:
84-
// Protected by mutex_.
76+
// Finished once the owning FDv1AdapterSynchronizer's close_promise_
77+
// is resolved. Read in Notify to drop late results.
78+
async::Future<std::monostate> const closed_future_;
79+
8580
mutable std::mutex mutex_;
86-
bool started_ = false;
87-
bool closed_ = false;
81+
// Protected by mutex_.
8882
std::optional<async::Promise<data_interfaces::FDv2SourceResult>>
8983
pending_promise_;
9084
std::deque<data_interfaces::FDv2SourceResult> result_queue_;
@@ -108,6 +102,10 @@ class FDv1AdapterSynchronizer final
108102
std::weak_ptr<State> state_;
109103
};
110104

105+
// Thread-safe primitive. Declared before state_ so state_'s constructor
106+
// can take a future from it.
107+
async::Promise<std::monostate> close_promise_;
108+
111109
// shared_ptr so async callbacks that may fire after this is destroyed
112110
// can hold their own reference.
113111
std::shared_ptr<State> const state_;
@@ -121,8 +119,13 @@ class FDv1AdapterSynchronizer final
121119

122120
std::unique_ptr<data_interfaces::IDataSynchronizer> const fdv1_source_;
123121

124-
// Thread-safe primitive.
125-
async::Promise<std::monostate> close_promise_;
122+
// Serializes StartAsync and ShutdownAsync on fdv1_source_ across
123+
// concurrent Next() and Close() calls.
124+
std::mutex lifecycle_mutex_;
125+
// Protected by lifecycle_mutex_. Set when Next() calls StartAsync, or
126+
// when Close() runs without a prior start (to gate any later Next()
127+
// from calling StartAsync after Close).
128+
bool started_ = false;
126129
};
127130

128131
} // namespace launchdarkly::server_side::data_systems

0 commit comments

Comments
 (0)