@@ -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