From 4dd66b9cdb7d3ddb74db216aaea5cae54d63acb3 Mon Sep 17 00:00:00 2001 From: Gauri Kalra Date: Tue, 14 Jul 2026 06:27:36 +0000 Subject: [PATCH] fix(storage): prevent indefinite hang when calling Close() on async appendable uploads --- .../storage/internal/async/writer_connection_impl.cc | 12 +++++++++++- .../internal/async/writer_connection_impl_test.cc | 10 ++++++++++ .../storage/tests/async_client_integration_test.cc | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/google/cloud/storage/internal/async/writer_connection_impl.cc b/google/cloud/storage/internal/async/writer_connection_impl.cc index 3acafa12d7dd8..a27ea4fbc3070 100644 --- a/google/cloud/storage/internal/async/writer_connection_impl.cc +++ b/google/cloud/storage/internal/async/writer_connection_impl.cc @@ -225,7 +225,17 @@ future AsyncWriterConnectionImpl::OnClose(std::size_t upload_size, HandleFinishAfterError("Expected Finish() error after non-ok Write()")); } offset_ += upload_size; - return Finish().then([](auto f) { return f.get(); }); + std::unique_lock lk(mu_); + auto impl = impl_; + lk.unlock(); + return impl->Read() + .then([this](auto f) { return OnQuery(f.get()); }) + .then([this](auto g) { + auto status = g.get(); + if (!status) return make_ready_future(std::move(status).status()); + return Finish(); + }) + .then([](auto f) { return f.get(); }); } future> diff --git a/google/cloud/storage/internal/async/writer_connection_impl_test.cc b/google/cloud/storage/internal/async/writer_connection_impl_test.cc index 03ecf7699cc2c..906ec74480614 100644 --- a/google/cloud/storage/internal/async/writer_connection_impl_test.cc +++ b/google/cloud/storage/internal/async/writer_connection_impl_test.cc @@ -850,6 +850,12 @@ TEST(AsyncWriterConnectionTest, CloseEmpty) { "test-only-algo"); return sequencer.PushBack("Write"); }); + EXPECT_CALL(*mock, Read).WillOnce([&] { + return sequencer.PushBack("Read").then([](auto f) { + if (!f.get()) return absl::optional(); + return absl::make_optional(Response{}); + }); + }); EXPECT_CALL(*mock, Finish).WillOnce([&] { return sequencer.PushBack("Finish").then([](auto f) { if (f.get()) return Status{}; @@ -867,6 +873,10 @@ TEST(AsyncWriterConnectionTest, CloseEmpty) { ASSERT_THAT(next.second, "Write"); next.first.set_value(true); + next = sequencer.PopFrontWithName(); + ASSERT_THAT(next.second, "Read"); + next.first.set_value(true); + next = sequencer.PopFrontWithName(); ASSERT_THAT(next.second, "Finish"); next.first.set_value(true); diff --git a/google/cloud/storage/tests/async_client_integration_test.cc b/google/cloud/storage/tests/async_client_integration_test.cc index 6827ec4ec8f57..e383aeee30f14 100644 --- a/google/cloud/storage/tests/async_client_integration_test.cc +++ b/google/cloud/storage/tests/async_client_integration_test.cc @@ -810,7 +810,7 @@ TEST_F(AsyncClientIntegrationTest, ResumeAppendableObjectUpload) { token = *std::move(p); } - writer.Close(); + ASSERT_STATUS_OK(writer.Close().get()); // Reset the existing writer and resume the upload. writer = AsyncWriter();