Skip to content

Commit 3b96c4b

Browse files
fix(storage): Fix Resume() to use append_object_spec instead of write_object_spec for resumed appendable uploads (#15558)
1 parent c72fcb3 commit 3b96c4b

2 files changed

Lines changed: 76 additions & 4 deletions

File tree

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -324,10 +324,16 @@ class AsyncWriterConnectionResumedState
324324
void Resume(Status const& s) {
325325
auto proto_status = ExtractGrpcStatus(s);
326326
auto request = google::storage::v2::BidiWriteObjectRequest{};
327-
auto spec = initial_request_.write_object_spec();
328327
auto& append_object_spec = *request.mutable_append_object_spec();
329-
append_object_spec.set_bucket(spec.resource().bucket());
330-
append_object_spec.set_object(spec.resource().name());
328+
if (initial_request_.has_write_object_spec()) {
329+
auto const& spec = initial_request_.write_object_spec();
330+
append_object_spec.set_bucket(spec.resource().bucket());
331+
append_object_spec.set_object(spec.resource().name());
332+
} else {
333+
auto const& spec = initial_request_.append_object_spec();
334+
append_object_spec.set_bucket(spec.bucket());
335+
append_object_spec.set_object(spec.object());
336+
}
331337
append_object_spec.set_generation(first_response_.resource().generation());
332338
ApplyWriteRedirectErrors(append_object_spec, std::move(proto_status));
333339

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

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ TEST(WriteConnectionResumed, FlushNonEmpty) {
258258
EXPECT_THAT(write.get(), StatusIs(StatusCode::kOk));
259259
}
260260

261-
TEST(WriteConnectionResumed, ResumeUsesGenerationFromFirstResponse) {
261+
TEST(WriteConnectionResumed, ResumeUsesWriteObjectSpecFromInitialRequest) {
262262
AsyncSequencer<bool> sequencer;
263263
auto mock = std::make_unique<MockAsyncWriterConnection>();
264264
auto initial_request = google::storage::v2::BidiWriteObjectRequest{};
@@ -316,6 +316,72 @@ TEST(WriteConnectionResumed, ResumeUsesGenerationFromFirstResponse) {
316316
// factory.
317317
EXPECT_THAT(write.get(), StatusIs(StatusCode::kAborted));
318318

319+
EXPECT_FALSE(captured_request.has_write_object_spec());
320+
EXPECT_TRUE(captured_request.has_append_object_spec());
321+
EXPECT_EQ(captured_request.append_object_spec().generation(), 12345);
322+
EXPECT_EQ(captured_request.append_object_spec().object(), "test-object");
323+
EXPECT_EQ(captured_request.append_object_spec().bucket(),
324+
"projects/_/buckets/test-bucket");
325+
}
326+
327+
TEST(WriteConnectionResumed, ResumeUsesAppendObjectSpecFromInitialRequest) {
328+
AsyncSequencer<bool> sequencer;
329+
auto mock = std::make_unique<MockAsyncWriterConnection>();
330+
auto initial_request = google::storage::v2::BidiWriteObjectRequest{};
331+
initial_request.mutable_append_object_spec()->set_bucket(
332+
"projects/_/buckets/test-bucket");
333+
initial_request.mutable_append_object_spec()->set_object("test-object");
334+
335+
google::storage::v2::BidiWriteObjectResponse first_response;
336+
first_response.mutable_resource()->set_generation(12345);
337+
338+
EXPECT_CALL(*mock, PersistedState)
339+
.WillRepeatedly(Return(MakePersistedState(0)));
340+
341+
// Expect Flush to be called because flush_ is true by default.
342+
// Make it fail to trigger Resume().
343+
EXPECT_CALL(*mock, Flush(_)).WillOnce([&](auto) {
344+
return sequencer.PushBack("Flush").then([](auto f) {
345+
if (f.get()) return google::cloud::Status{}; // Should not be true
346+
return TransientError();
347+
});
348+
});
349+
350+
MockFactory mock_factory;
351+
google::storage::v2::BidiWriteObjectRequest captured_request;
352+
EXPECT_CALL(mock_factory, Call(_))
353+
.WillOnce([&](google::storage::v2::BidiWriteObjectRequest request) {
354+
captured_request = std::move(request);
355+
return sequencer.PushBack("Factory").then([](auto) {
356+
return StatusOr<WriteObject::WriteResult>(
357+
internal::AbortedError("stop test", GCP_ERROR_INFO()));
358+
});
359+
});
360+
361+
auto connection = MakeWriterConnectionResumed(
362+
mock_factory.AsStdFunction(), std::move(mock), initial_request, nullptr,
363+
first_response, Options{});
364+
365+
// This will call FlushStep -> mock->Flush()
366+
auto write = connection->Write(TestPayload(1));
367+
ASSERT_FALSE(write.is_ready());
368+
369+
// Trigger the Flush error in the mock
370+
auto next = sequencer.PopFrontWithName();
371+
EXPECT_EQ(next.second, "Flush");
372+
next.first.set_value(false); // This makes the lambda return TransientError
373+
374+
// The error in OnFlush triggers Resume(), which calls the mock_factory.
375+
// Allow the factory callback to proceed.
376+
next = sequencer.PopFrontWithName();
377+
EXPECT_EQ(next.second, "Factory");
378+
next.first.set_value(true);
379+
380+
// The write future should now be ready, containing the error from the
381+
// factory.
382+
EXPECT_THAT(write.get(), StatusIs(StatusCode::kAborted));
383+
384+
EXPECT_FALSE(captured_request.has_write_object_spec());
319385
EXPECT_TRUE(captured_request.has_append_object_spec());
320386
EXPECT_EQ(captured_request.append_object_spec().generation(), 12345);
321387
EXPECT_EQ(captured_request.append_object_spec().object(), "test-object");

0 commit comments

Comments
 (0)