Skip to content

Commit 573fc26

Browse files
qyryqGazizonoki
authored andcommitted
Moved "TFederatedWriteSession: Wait for messages to be sent in Close method" commit from ydb repo
1 parent f29bf1e commit 573fc26

File tree

4 files changed

+150
-89
lines changed

4 files changed

+150
-89
lines changed

include/ydb-cpp-sdk/client/topic/topic.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1557,8 +1557,8 @@ class IWriteSession {
15571557
std::optional<ui64> seqNo = std::nullopt, std::optional<TInstant> createTimestamp = std::nullopt) = 0;
15581558

15591559

1560-
//! Wait for all writes to complete (no more that closeTimeout()), than close. Empty maybe - means infinite timeout.
1561-
//! return - true if all writes were completed and acked. false if timeout was reached and some writes were aborted.
1560+
//! Wait for all writes to complete (no more that closeTimeout()), then close.
1561+
//! Return true if all writes were completed and acked, false if timeout was reached and some writes were aborted.
15621562
virtual bool Close(TDuration closeTimeout = TDuration::Max()) = 0;
15631563

15641564
//! Writer counters with different stats (see TWriterConuters).

src/client/federated_topic/impl/federated_write_session.cpp

Lines changed: 121 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ NTopic::TTopicClientSettings FromFederated(const TFederatedTopicClientSettings&
2727
TFederatedWriteSessionImpl::TFederatedWriteSessionImpl(
2828
const TFederatedWriteSessionSettings& settings,
2929
std::shared_ptr<TGRpcConnectionsImpl> connections,
30-
const TFederatedTopicClientSettings& clientSetttings,
30+
const TFederatedTopicClientSettings& clientSettings,
3131
std::shared_ptr<TFederatedDbObserver> observer,
3232
std::shared_ptr<std::unordered_map<NTopic::ECodec, THolder<NTopic::ICodec>>> codecs
3333
)
3434
: Settings(settings)
3535
, Connections(std::move(connections))
36-
, SubClientSetttings(FromFederated(clientSetttings))
36+
, SubclientSettings(FromFederated(clientSettings))
3737
, ProvidedCodecs(std::move(codecs))
3838
, Observer(std::move(observer))
3939
, AsyncInit(Observer->WaitForFirstState())
@@ -44,37 +44,72 @@ TFederatedWriteSessionImpl::TFederatedWriteSessionImpl(
4444
{
4545
}
4646

47-
TStringBuilder TFederatedWriteSessionImpl::GetLogPrefix() const {
48-
return TStringBuilder() << GetDatabaseLogPrefix(SubClientSetttings.Database_.value_or("")) << "[" << SessionId << "] ";
47+
TStringBuilder TFederatedWriteSessionImpl::GetLogPrefixImpl() const {
48+
return TStringBuilder() << GetDatabaseLogPrefix(SubclientSettings.Database_.value_or("")) << "[" << SessionId << "] ";
49+
}
50+
51+
52+
bool TFederatedWriteSessionImpl::MessageQueuesAreEmptyImpl() const {
53+
Y_ABORT_UNLESS(Lock.IsLocked());
54+
return OriginalMessagesToGetAck.empty() && OriginalMessagesToPassDown.empty();
55+
}
56+
57+
void TFederatedWriteSessionImpl::IssueTokenIfAllowed() {
58+
// The session should not issue tokens after it has transitioned to CLOSING or CLOSE state.
59+
// A user may have one spare token, so at most one additional message
60+
// could be written to the internal queue after the transition.
61+
bool issue = false;
62+
with_lock(Lock) {
63+
if (BufferFreeSpace > 0 && !ClientHasToken && SessionState < State::CLOSING) {
64+
ClientHasToken = true;
65+
issue = true;
66+
}
67+
}
68+
if (issue) {
69+
ClientEventsQueue->PushEvent(NTopic::TWriteSessionEvent::TReadyToAcceptEvent{IssueContinuationToken()});
70+
}
71+
}
72+
73+
void TFederatedWriteSessionImpl::UpdateFederationStateImpl() {
74+
Y_ABORT_UNLESS(Lock.IsLocked());
75+
// Even after the user has called the Close method, transitioning the session to the CLOSING state,
76+
// we keep updating the federation state, as the session may still have some messages to send in its queues,
77+
// and for that we need to know the current state of the federation.
78+
if (SessionState < State::CLOSED) {
79+
FederationState = Observer->GetState();
80+
OnFederationStateUpdateImpl();
81+
}
4982
}
5083

5184
void TFederatedWriteSessionImpl::Start() {
5285
// TODO validate settings?
53-
Settings.EventHandlers_.HandlersExecutor_->Start();
5486
with_lock(Lock){
55-
ClientEventsQueue->PushEvent(NTopic::TWriteSessionEvent::TReadyToAcceptEvent{IssueContinuationToken()});
56-
ClientHasToken = true;
87+
if (SessionState != State::CREATED) {
88+
return;
89+
}
90+
SessionState = State::WORKING;
91+
Settings.EventHandlers_.HandlersExecutor_->Start();
5792
}
5893

59-
AsyncInit.Subscribe([selfCtx = SelfContext](const auto& f){
94+
IssueTokenIfAllowed();
95+
96+
AsyncInit.Subscribe([selfCtx = SelfContext](const auto& f) {
6097
Y_UNUSED(f);
6198
if (auto self = selfCtx->LockShared()) {
6299
with_lock(self->Lock) {
63-
if (!self->Closing) {
64-
self->FederationState = self->Observer->GetState();
65-
self->OnFederatedStateUpdateImpl();
66-
}
100+
self->UpdateFederationStateImpl();
67101
}
68102
}
69103
});
70104
}
71105

72-
void TFederatedWriteSessionImpl::OpenSubSessionImpl(std::shared_ptr<TDbInfo> db) {
106+
void TFederatedWriteSessionImpl::OpenSubsessionImpl(std::shared_ptr<TDbInfo> db) {
107+
Y_ABORT_UNLESS(Lock.IsLocked());
73108
if (Subsession) {
74109
PendingToken.reset();
75110
Subsession->Close(TDuration::Zero());
76111
}
77-
NTopic::TTopicClientSettings clientSettings = SubClientSetttings;
112+
auto clientSettings = SubclientSettings;
78113
clientSettings
79114
.Database(db->path())
80115
.DiscoveryEndpoint(db->endpoint());
@@ -89,7 +124,7 @@ void TFederatedWriteSessionImpl::OpenSubSessionImpl(std::shared_ptr<TDbInfo> db)
89124
with_lock(self->Lock) {
90125
Y_ABORT_UNLESS(!self->PendingToken.has_value());
91126
self->PendingToken = std::move(ev.ContinuationToken);
92-
self->PrepareDeferredWrite(deferred);
127+
self->PrepareDeferredWriteImpl(deferred);
93128
}
94129
deferred.DoWrite();
95130
}
@@ -98,24 +133,25 @@ void TFederatedWriteSessionImpl::OpenSubSessionImpl(std::shared_ptr<TDbInfo> db)
98133
if (auto self = selfCtx->LockShared()) {
99134
with_lock(self->Lock) {
100135
Y_ABORT_UNLESS(ev.Acks.size() <= self->OriginalMessagesToGetAck.size());
136+
101137
for (size_t i = 0; i < ev.Acks.size(); ++i) {
102138
self->BufferFreeSpace += self->OriginalMessagesToGetAck.front().Data.size();
103139
self->OriginalMessagesToGetAck.pop_front();
104140
}
105-
self->ClientEventsQueue->PushEvent(std::move(ev));
106-
if (self->BufferFreeSpace > 0 && !self->ClientHasToken) {
107-
self->ClientEventsQueue->PushEvent(NTopic::TWriteSessionEvent::TReadyToAcceptEvent{IssueContinuationToken()});
108-
self->ClientHasToken = true;
141+
142+
if (self->MessageQueuesAreEmptyImpl() && self->MessageQueuesHaveBeenEmptied.Initialized() && !self->MessageQueuesHaveBeenEmptied.HasValue()) {
143+
self->MessageQueuesHaveBeenEmptied.SetValue();
109144
}
110145
}
146+
147+
self->ClientEventsQueue->PushEvent(std::move(ev));
148+
self->IssueTokenIfAllowed();
111149
}
112150
})
113151
.SessionClosedHandler([selfCtx = SelfContext](const NTopic::TSessionClosedEvent & ev) {
114152
if (auto self = selfCtx->LockShared()) {
115153
with_lock(self->Lock) {
116-
if (!self->Closing) {
117-
self->CloseImpl(ev);
118-
}
154+
self->CloseImpl(ev);
119155
}
120156
}
121157
});
@@ -129,7 +165,7 @@ void TFederatedWriteSessionImpl::OpenSubSessionImpl(std::shared_ptr<TDbInfo> db)
129165
CurrentDatabase = db;
130166
}
131167

132-
std::pair<std::shared_ptr<TDbInfo>, EStatus> SelectDatabaseByHash(
168+
std::pair<std::shared_ptr<TDbInfo>, EStatus> SelectDatabaseByHashImpl(
133169
NTopic::TFederatedWriteSessionSettings const& settings,
134170
std::vector<std::shared_ptr<TDbInfo>> const& dbInfos
135171
) {
@@ -163,7 +199,7 @@ std::pair<std::shared_ptr<TDbInfo>, EStatus> SelectDatabaseByHash(
163199
Y_UNREACHABLE();
164200
}
165201

166-
std::pair<std::shared_ptr<TDbInfo>, EStatus> SelectDatabase(
202+
std::pair<std::shared_ptr<TDbInfo>, EStatus> SelectDatabaseImpl(
167203
NTopic::TFederatedWriteSessionSettings const& settings,
168204
std::vector<std::shared_ptr<TDbInfo>> const& dbInfos, std::string const& selfLocation
169205
) {
@@ -197,7 +233,7 @@ std::pair<std::shared_ptr<TDbInfo>, EStatus> SelectDatabase(
197233
if (!settings.AllowFallback_) {
198234
return {nullptr, EStatus::NOT_FOUND};
199235
}
200-
return SelectDatabaseByHash(settings, dbInfos);
236+
return SelectDatabaseByHashImpl(settings, dbInfos);
201237
}
202238
}
203239

@@ -208,69 +244,64 @@ std::pair<std::shared_ptr<TDbInfo>, EStatus> SelectDatabase(
208244
if (!settings.AllowFallback_) {
209245
return {nullptr, EStatus::UNAVAILABLE};
210246
}
211-
return SelectDatabaseByHash(settings, dbInfos);
212-
}
213-
214-
std::pair<std::shared_ptr<TDbInfo>, EStatus> TFederatedWriteSessionImpl::SelectDatabaseImpl() {
215-
return SelectDatabase(Settings, FederationState->DbInfos, FederationState->SelfLocation);
247+
return SelectDatabaseByHashImpl(settings, dbInfos);
216248
}
217249

218-
void TFederatedWriteSessionImpl::OnFederatedStateUpdateImpl() {
250+
void TFederatedWriteSessionImpl::OnFederationStateUpdateImpl() {
251+
Y_ABORT_UNLESS(Lock.IsLocked());
219252
if (!FederationState->Status.IsSuccess()) {
253+
// The observer became stale, it won't try to get federation state anymore due to retry policy,
254+
// so there's no reason to keep the write session alive.
220255
CloseImpl(FederationState->Status.GetStatus(), NYql::TIssues(FederationState->Status.GetIssues()));
221256
return;
222257
}
223258

224259
Y_ABORT_UNLESS(!FederationState->DbInfos.empty());
225260

226-
auto [preferrableDb, status] = SelectDatabaseImpl();
261+
auto [preferrableDb, status] = SelectDatabaseImpl(Settings, FederationState->DbInfos, FederationState->SelfLocation);
227262

228263
if (!preferrableDb) {
229264
if (!RetryState) {
230265
RetryState = Settings.RetryPolicy_->CreateRetryState();
231266
}
232267
if (auto delay = RetryState->GetNextRetryDelay(status)) {
233-
LOG_LAZY(Log, TLOG_NOTICE, GetLogPrefix() << "Retry to update federation state in " << delay);
234-
ScheduleFederatedStateUpdateImpl(*delay);
268+
LOG_LAZY(Log, TLOG_NOTICE, GetLogPrefixImpl() << "Retry to update federation state in " << delay);
269+
ScheduleFederationStateUpdateImpl(*delay);
235270
} else {
236271
std::string message = "Failed to select database: no available database";
237-
LOG_LAZY(Log, TLOG_ERR, GetLogPrefix() << message);
272+
LOG_LAZY(Log, TLOG_ERR, GetLogPrefixImpl() << message << ". Status: " << status);
238273
CloseImpl(status, NYql::TIssues{NYql::TIssue(message)});
239274
}
240275
return;
241276
}
242277
RetryState.reset();
243278

244279
if (!DatabasesAreSame(preferrableDb, CurrentDatabase)) {
245-
LOG_LAZY(Log, TLOG_INFO, GetLogPrefix()
280+
LOG_LAZY(Log, TLOG_INFO, GetLogPrefixImpl()
246281
<< "Start federated write session to database '" << preferrableDb->name()
247282
<< "' (previous was " << (CurrentDatabase ? CurrentDatabase->name() : "<empty>") << ")"
248283
<< " FederationState: " << *FederationState);
249-
OpenSubSessionImpl(preferrableDb);
284+
OpenSubsessionImpl(preferrableDb);
250285
}
251286

252-
ScheduleFederatedStateUpdateImpl(UPDATE_FEDERATION_STATE_DELAY);
287+
ScheduleFederationStateUpdateImpl(UPDATE_FEDERATION_STATE_DELAY);
253288
}
254289

255-
void TFederatedWriteSessionImpl::ScheduleFederatedStateUpdateImpl(TDuration delay) {
290+
void TFederatedWriteSessionImpl::ScheduleFederationStateUpdateImpl(TDuration delay) {
256291
Y_ABORT_UNLESS(Lock.IsLocked());
257292
auto cb = [selfCtx = SelfContext](bool ok) {
258293
if (ok) {
259294
if (auto self = selfCtx->LockShared()) {
260295
with_lock(self->Lock) {
261-
if (self->Closing) {
262-
return;
263-
}
264-
self->FederationState = self->Observer->GetState();
265-
self->OnFederatedStateUpdateImpl();
296+
self->UpdateFederationStateImpl();
266297
}
267298
}
268299
}
269300
};
270301

271302
UpdateStateDelayContext = Connections->CreateContext();
272303
if (!UpdateStateDelayContext) {
273-
Closing = true;
304+
CloseImpl(EStatus::TRANSPORT_UNAVAILABLE, NYql::TIssues{NYql::TIssue("Could not update federation state")});
274305
// TODO log DRIVER_IS_STOPPING_DESCRIPTION
275306
return;
276307
}
@@ -325,28 +356,25 @@ void TFederatedWriteSessionImpl::WriteEncoded(NTopic::TContinuationToken&& token
325356
}
326357

327358
void TFederatedWriteSessionImpl::WriteInternal(NTopic::TContinuationToken&&, TWrappedWriteMessage&& wrapped) {
328-
ClientHasToken = false;
329-
if (!wrapped.Message.CreateTimestamp_.has_value()) {
330-
wrapped.Message.CreateTimestamp_ = TInstant::Now();
331-
}
359+
TDeferredWrite deferred(Subsession);
332360

333-
{
334-
TDeferredWrite deferred(Subsession);
335-
with_lock(Lock) {
336-
BufferFreeSpace -= wrapped.Message.Data.size();
337-
OriginalMessagesToPassDown.emplace_back(std::move(wrapped));
338-
339-
PrepareDeferredWrite(deferred);
361+
with_lock(Lock) {
362+
ClientHasToken = false;
363+
if (!wrapped.Message.CreateTimestamp_.has_value()) {
364+
wrapped.Message.CreateTimestamp_ = TInstant::Now();
340365
}
341-
deferred.DoWrite();
342-
}
343-
if (BufferFreeSpace > 0) {
344-
ClientEventsQueue->PushEvent(NTopic::TWriteSessionEvent::TReadyToAcceptEvent{IssueContinuationToken()});
345-
ClientHasToken = true;
366+
BufferFreeSpace -= wrapped.Message.Data.size();
367+
OriginalMessagesToPassDown.emplace_back(std::move(wrapped));
368+
PrepareDeferredWriteImpl(deferred);
346369
}
370+
371+
deferred.DoWrite();
372+
373+
IssueTokenIfAllowed();
347374
}
348375

349-
bool TFederatedWriteSessionImpl::PrepareDeferredWrite(TDeferredWrite& deferred) {
376+
bool TFederatedWriteSessionImpl::PrepareDeferredWriteImpl(TDeferredWrite& deferred) {
377+
Y_ABORT_UNLESS(Lock.IsLocked());
350378
if (!PendingToken.has_value()) {
351379
return false;
352380
}
@@ -361,27 +389,47 @@ bool TFederatedWriteSessionImpl::PrepareDeferredWrite(TDeferredWrite& deferred)
361389
return true;
362390
}
363391

364-
void TFederatedWriteSessionImpl::CloseImpl(EStatus statusCode, NYql::TIssues&& issues, TDuration timeout) {
365-
CloseImpl(TPlainStatus(statusCode, std::move(issues)), timeout);
392+
void TFederatedWriteSessionImpl::CloseImpl(EStatus statusCode, NYql::TIssues&& issues) {
393+
CloseImpl(TPlainStatus(statusCode, std::move(issues)));
366394
}
367395

368-
void TFederatedWriteSessionImpl::CloseImpl(NTopic::TSessionClosedEvent const& ev, TDuration timeout) {
369-
if (Closing) {
396+
void TFederatedWriteSessionImpl::CloseImpl(NTopic::TSessionClosedEvent const& ev) {
397+
Y_ABORT_UNLESS(Lock.IsLocked());
398+
if (SessionState == State::CLOSED) {
370399
return;
371400
}
372-
Closing = true;
373-
if (Subsession) {
374-
Subsession->Close(timeout);
375-
}
376-
ClientEventsQueue->Close(ev);
401+
SessionState = State::CLOSED;
377402
NTopic::Cancel(UpdateStateDelayContext);
403+
if (!HasBeenClosed.HasValue()) {
404+
HasBeenClosed.SetValue();
405+
}
406+
{
407+
auto unguard = Unguard(Lock);
408+
ClientEventsQueue->Close(ev);
409+
}
378410
}
379411

380412
bool TFederatedWriteSessionImpl::Close(TDuration timeout) {
381413
with_lock(Lock) {
382-
CloseImpl(EStatus::SUCCESS, {}, timeout);
414+
if (SessionState == State::CLOSED) {
415+
return MessageQueuesAreEmptyImpl();
416+
}
417+
SessionState = State::CLOSING;
418+
if (!MessageQueuesHaveBeenEmptied.Initialized()) {
419+
MessageQueuesHaveBeenEmptied = NThreading::NewPromise();
420+
if (MessageQueuesAreEmptyImpl()) {
421+
MessageQueuesHaveBeenEmptied.SetValue();
422+
}
423+
}
424+
}
425+
426+
std::vector<NThreading::TFuture<void>> futures{MessageQueuesHaveBeenEmptied.GetFuture(), HasBeenClosed.GetFuture()};
427+
NThreading::WaitAny(futures).Wait(timeout);
428+
429+
with_lock(Lock) {
430+
CloseImpl(EStatus::SUCCESS, NYql::TIssues{});
431+
return MessageQueuesAreEmptyImpl();
383432
}
384-
return true;
385433
}
386434

387435
} // namespace NYdb::NFederatedTopic

0 commit comments

Comments
 (0)