Skip to content

Commit 1da8c41

Browse files
committed
fix(storage): add Close() support to AsyncWriterConnectionResumed
1 parent ce5aa34 commit 1da8c41

2 files changed

Lines changed: 392 additions & 59 deletions

File tree

google/cloud/storage/internal/async/writer_connection_resumed.cc

Lines changed: 156 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ class AsyncWriterConnectionResumedState
7777
buffer_size_lwm_(buffer_size_lwm),
7878
buffer_size_hwm_(buffer_size_hwm) {
7979
finalized_future_ = finalized_.get_future();
80+
closed_future_ = closed_.get_future();
8081
options_ = internal::MakeImmutableOptions(options);
8182
auto state = impl_->PersistedState();
8283
if (absl::holds_alternative<google::storage::v2::Object>(state)) {
@@ -145,6 +146,19 @@ class AsyncWriterConnectionResumedState
145146
return f;
146147
}
147148

149+
future<Status> Close(storage::WritePayload const& p) {
150+
std::unique_lock<std::mutex> lk(mu_);
151+
if (close_ || closed_promise_completed_) {
152+
return make_ready_future(internal::FailedPreconditionError(
153+
"Close() already called", GCP_ERROR_INFO()));
154+
}
155+
resend_buffer_.Append(WritePayloadImpl::GetImpl(p));
156+
close_ = true;
157+
// Force flush to drain the buffer first.
158+
HandleNewData(std::move(lk), true);
159+
return std::move(closed_future_);
160+
}
161+
148162
future<StatusOr<std::int64_t>> Query() {
149163
return Impl(std::unique_lock<std::mutex>(mu_))->Query();
150164
}
@@ -219,6 +233,10 @@ class AsyncWriterConnectionResumedState
219233
// FinalizeStep will set the finalizing_ flag.
220234
return FinalizeStep(std::move(lk));
221235
}
236+
if (close_ && !closing_) {
237+
// CloseStep will set the closing_ flag.
238+
return CloseStep(std::move(lk));
239+
}
222240
// If not finalizing, check if an empty flush is needed.
223241
if (flush_) {
224242
state_ = State::kWriting;
@@ -254,6 +272,25 @@ class AsyncWriterConnectionResumedState
254272
SetFinalized(std::unique_lock<std::mutex>(mu_), std::move(result));
255273
}
256274

275+
void CloseStep(std::unique_lock<std::mutex> lk) {
276+
if (closing_ || state_ != State::kIdle) {
277+
return;
278+
}
279+
state_ = State::kWriting;
280+
closing_ = true;
281+
auto impl = Impl(lk);
282+
lk.unlock();
283+
(void)impl->Close(storage::WritePayload{})
284+
.then([w = WeakFromThis()](auto f) {
285+
if (auto self = w.lock()) return self->OnClose(f.get());
286+
});
287+
}
288+
289+
void OnClose(Status result) {
290+
if (!result.ok()) return Resume(std::move(result));
291+
SetClosed(std::unique_lock<std::mutex>(mu_), std::move(result));
292+
}
293+
257294
void FlushStep(std::unique_lock<std::mutex> lk, absl::Cord payload) {
258295
auto impl = Impl(lk);
259296
lk.unlock();
@@ -385,75 +422,58 @@ class AsyncWriterConnectionResumedState
385422
append_object_spec.set_generation(first_response_.resource().generation());
386423
ApplyWriteRedirectErrors(append_object_spec, std::move(proto_status));
387424

388-
// Capture the finalization state *before* starting the async resume.
425+
// Capture the finalization and close state *before* starting the async
426+
// resume.
389427
bool was_finalizing;
428+
bool was_closing;
390429
{
391430
std::unique_lock<std::mutex> lk(mu_);
392431
if (state_ == State::kResuming) return;
393432
was_finalizing = finalizing_;
433+
was_closing = closing_;
394434
if (!s.ok() && cancelled_) {
395435
return SetError(std::move(lk), std::move(s));
396436
}
397437
state_ = State::kResuming;
398438
}
399-
// Pass the original status `s` and `was_finalizing` to the callback.
439+
// Pass the original status `s`, `was_finalizing`, and `was_closing` to the
440+
// callback.
400441
factory_(std::move(request))
401-
.then([s, was_finalizing, w = WeakFromThis()](auto f) {
442+
.then([s, was_finalizing, was_closing, w = WeakFromThis()](auto f) {
402443
if (auto self = w.lock())
403-
return self->OnResume(s, was_finalizing, f.get());
444+
return self->OnResume(s, was_finalizing, was_closing, f.get());
404445
});
405446
}
406447

407448
void OnResume(Status const& original_status, bool was_finalizing,
408-
StatusOr<WriteObject::WriteResult> res) {
449+
bool was_closing, StatusOr<WriteObject::WriteResult> res) {
409450
std::unique_lock<std::mutex> lk(mu_);
410451
// Update write_handle from any resume response that contains it.
411452
if (res && res->first_response.has_write_handle()) {
412453
latest_write_handle_ = res->first_response.write_handle();
413454
}
414455

415-
if (was_finalizing) {
416-
// If resuming due to a finalization error, we *must* complete the
417-
// finalized_ promise now, based on the resume attempt's outcome.
418-
if (!res) {
419-
// The resume attempt itself failed. Use that error.
420-
return SetError(std::move(lk), std::move(res).status());
421-
}
422-
// Resume attempt succeeded, check the persisted state.
423-
auto state = impl_->PersistedState();
424-
if (absl::holds_alternative<google::storage::v2::Object>(state)) {
425-
// Resume found the object is finalized. Success.
426-
return SetFinalized(
427-
std::move(lk),
428-
absl::get<google::storage::v2::Object>(std::move(state)));
429-
}
430-
// Resume succeeded, but the object is still not finalized.
431-
// This means the original finalization attempt failed permanently.
432-
// Use the original status that triggered the resume. Reset finalizing_
433-
// before setting the error, as the attempt is now over.
434-
finalizing_ = false;
435-
return SetError(std::move(lk), std::move(original_status));
436-
}
437-
438-
// Resume was *not* triggered by finalization failure.
439456
if (!res) {
440-
// Regular resume attempt failed.
457+
// Resume attempt failed. SetError will complete everything.
441458
return SetError(std::move(lk), std::move(res).status());
442459
}
443-
// Regular resume attempt succeeded. Check state.
460+
461+
// Resume attempt succeeded. Check if finalized.
444462
std::int64_t persisted_offset = 0;
445463
absl::optional<google::storage::v2::ObjectChecksums> checksums;
464+
bool finalized_in_response = false;
465+
google::storage::v2::Object finalized_object;
446466

447467
if (res->first_response.has_resource()) {
448468
if (!res->first_response.has_write_handle()) {
449-
// Found finalized object (maybe finalized concurrently or resumed).
450-
return SetFinalized(std::move(lk),
451-
std::move(*res->first_response.mutable_resource()));
452-
}
453-
auto const& resource = res->first_response.resource();
454-
persisted_offset = resource.size();
455-
if (resource.has_checksums()) {
456-
checksums = resource.checksums();
469+
finalized_in_response = true;
470+
finalized_object = res->first_response.resource();
471+
} else {
472+
auto const& resource = res->first_response.resource();
473+
persisted_offset = resource.size();
474+
if (resource.has_checksums()) {
475+
checksums = resource.checksums();
476+
}
457477
}
458478
} else if (res->first_response.has_persisted_size()) {
459479
persisted_offset = res->first_response.persisted_size();
@@ -463,15 +483,37 @@ class AsyncWriterConnectionResumedState
463483
} else {
464484
auto state = impl_->PersistedState();
465485
if (absl::holds_alternative<google::storage::v2::Object>(state)) {
466-
// Found finalized object (maybe finalized concurrently or resumed).
467-
return SetFinalized(
468-
std::move(lk),
469-
absl::get<google::storage::v2::Object>(std::move(state)));
486+
finalized_in_response = true;
487+
finalized_object =
488+
absl::get<google::storage::v2::Object>(std::move(state));
489+
} else {
490+
persisted_offset = absl::get<std::int64_t>(state);
491+
checksums = impl_->PersistedChecksums();
470492
}
471-
persisted_offset = absl::get<std::int64_t>(state);
472-
checksums = impl_->PersistedChecksums();
473493
}
474494

495+
if (finalized_in_response) {
496+
if (was_finalizing) {
497+
return SetFinalized(std::move(lk), std::move(finalized_object));
498+
}
499+
if (was_closing) {
500+
return SetClosed(std::move(lk), Status{});
501+
}
502+
return SetFinalized(std::move(lk), std::move(finalized_object));
503+
}
504+
505+
// Resume succeeded, but not finalized.
506+
// If we were finalizing or closing, this is a failure of that operation.
507+
if (was_finalizing) {
508+
finalizing_ = false;
509+
return SetError(std::move(lk), std::move(original_status));
510+
}
511+
if (was_closing) {
512+
closing_ = false;
513+
return SetError(std::move(lk), std::move(original_status));
514+
}
515+
516+
// Recreate impl_
475517
auto hash = hash_function_;
476518
if (checksums && checksums->has_crc32c()) {
477519
hash = std::make_shared<
@@ -517,6 +559,30 @@ class AsyncWriterConnectionResumedState
517559
p.set_value(std::move(object)); // Set value on the moved promise
518560
}
519561

562+
void SetClosed(std::unique_lock<std::mutex> lk, Status status) {
563+
resend_buffer_.Clear();
564+
state_ = State::kIdle;
565+
close_ = false;
566+
closing_ = false;
567+
flush_ = false;
568+
// Check if the promise has already been completed.
569+
if (closed_promise_completed_) {
570+
lk.unlock(); // Release lock before returning
571+
return;
572+
}
573+
// Mark the promise as completed before moving it.
574+
closed_promise_completed_ = true;
575+
auto handlers = ClearHandlers(lk);
576+
// Also clear any pending flush promises on success.
577+
auto pending_flushes = std::move(pending_flush_promises_);
578+
auto p = std::move(closed_); // Move the member promise.
579+
lk.unlock();
580+
// Notify handlers and pending flushes after releasing the lock.
581+
for (auto& h : handlers) h->Execute(status);
582+
for (auto& pf : pending_flushes) pf.set_value(status);
583+
p.set_value(std::move(status)); // Set value on the moved promise.
584+
}
585+
520586
void SetFlushed(std::unique_lock<std::mutex> lk, Status const& result) {
521587
if (!result.ok()) return SetError(std::move(lk), std::move(result));
522588
flush_ = false; // Reset flush flag; WriteLoop may set it again.
@@ -546,27 +612,32 @@ class AsyncWriterConnectionResumedState
546612
state_ = State::kIdle;
547613
finalize_ = false;
548614
finalizing_ = false; // Reset finalizing flag
615+
close_ = false;
616+
closing_ = false; // Reset closing flag
549617
flush_ = false;
550618

551619
// Always clear handlers and pending flushes on error.
552620
auto handlers = ClearHandlers(lk);
553621
auto pending_flushes = std::move(pending_flush_promises_);
554622

555623
// Check if the finalized promise has already been completed.
556-
if (finalized_promise_completed_) {
557-
// Finalized promise already set, just notify handlers and pending
558-
// flushes.
559-
lk.unlock(); // Release lock before notifying
560-
for (auto& h : handlers) h->Execute(status);
561-
for (auto& pf : pending_flushes) pf.set_value(status);
562-
return;
624+
bool complete_finalized = false;
625+
promise<StatusOr<google::storage::v2::Object>> finalized_to_complete;
626+
if (!finalized_promise_completed_) {
627+
finalized_promise_completed_ = true;
628+
finalized_to_complete = std::move(finalized_);
629+
complete_finalized = true;
630+
}
631+
632+
// Check if the closed promise has already been completed.
633+
bool complete_closed = false;
634+
promise<Status> closed_to_complete;
635+
if (!closed_promise_completed_) {
636+
closed_promise_completed_ = true;
637+
closed_to_complete = std::move(closed_);
638+
complete_closed = true;
563639
}
564640

565-
// Mark the finalized promise as completed *before* moving it under the
566-
// lock.
567-
finalized_promise_completed_ = true;
568-
// Move the finalized promise.
569-
auto p = std::move(finalized_);
570641
lk.unlock(); // Release lock before notifying
571642

572643
// Notify handlers first.
@@ -575,8 +646,13 @@ class AsyncWriterConnectionResumedState
575646
for (auto& pf : pending_flushes) {
576647
pf.set_value(status);
577648
}
578-
// Set error on the moved finalized promise *once*.
579-
p.set_value(status);
649+
// Set error on the moved promises *once*.
650+
if (complete_finalized) {
651+
finalized_to_complete.set_value(status);
652+
}
653+
if (complete_closed) {
654+
closed_to_complete.set_value(status);
655+
}
580656
}
581657

582658
std::shared_ptr<storage::AsyncWriterConnection> Impl(
@@ -630,6 +706,14 @@ class AsyncWriterConnectionResumedState
630706
// finalized_.
631707
future<StatusOr<google::storage::v2::Object>> finalized_future_;
632708

709+
// The result of calling `Close()`. Note that only one such call is ever
710+
// made.
711+
promise<Status> closed_;
712+
713+
// Retrieve the future in the constructor, as some operations reset
714+
// closed_.
715+
future<Status> closed_future_;
716+
633717
// Queue of promises for outstanding Flush() calls.
634718
std::deque<promise<Status>> pending_flush_promises_;
635719

@@ -682,6 +766,15 @@ class AsyncWriterConnectionResumedState
682766
// Tracks if the final promise (`finalized_`) has been completed.
683767
bool finalized_promise_completed_ = false;
684768

769+
// If true, all the data to close an upload is in `resend_buffer_`.
770+
bool close_ = false;
771+
772+
// True if CloseStep has been initiated. Prevents re-entry.
773+
bool closing_ = false;
774+
775+
// Tracks if the final promise (`closed_`) has been completed.
776+
bool closed_promise_completed_ = false;
777+
685778
// Track the latest write handle seen in responses.
686779
absl::optional<google::storage::v2::BidiWriteHandle> latest_write_handle_;
687780
};
@@ -762,6 +855,10 @@ class AsyncWriterConnectionResumed : public storage::AsyncWriterConnection {
762855
return state_->Flush(std::move(p));
763856
}
764857

858+
future<Status> Close(storage::WritePayload p) override {
859+
return state_->Close(std::move(p));
860+
}
861+
765862
future<StatusOr<std::int64_t>> Query() override { return state_->Query(); }
766863

767864
RpcMetadata GetRequestMetadata() override {

0 commit comments

Comments
 (0)