Skip to content

Add content length to PUT GCP multipart complete#257

Merged
alamb merged 4 commits into
apache:mainfrom
jkosh44:gcp-multipart-complete
Mar 24, 2025
Merged

Add content length to PUT GCP multipart complete#257
alamb merged 4 commits into
apache:mainfrom
jkosh44:gcp-multipart-complete

Conversation

@jkosh44

@jkosh44 jkosh44 commented Mar 20, 2025

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Rationale for this change

Fixes unreported error

What changes are included in this PR?

This commit fixes the GCP mulipart complete implementation by adding the Content-Length header to the PUT XML requests that happens when the completed parts are empty. GCP is strict about setting the Content-Length header on requests with empty bodies, so previously this would result in a 411 error.

Are there any user-facing changes?

Yes, empty multipart uploads don't cause 411 in GCP.

This commit fixes the GCP mulipart complete implementation by adding
the Content-Length header to the PUT XML requests that happens when
the completed parts are empty. GCP is strict about setting the
Content-Length header on requests with empty bodies, so previously this
would result in a 411 error.

@alamb alamb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @jkosh44

@jkosh44

jkosh44 commented Mar 20, 2025

Copy link
Copy Markdown
Contributor Author

@alamb I wasn't able to come up with a good unit test for this.

@alamb

alamb commented Mar 21, 2025

Copy link
Copy Markdown
Contributor

Thanks @jkosh44 -- I am going to wait to merge this PR for a day or so to let some of the other maintainers double check I got this new repo set up correctly, but I think this PR is ready to merge from my perspective

Comment thread src/gcp/client.rs
Comment on lines 518 to 523
let result = self
.request(Method::PUT, path)
.header(&CONTENT_LENGTH, "0")
.idempotent(true)
.do_put()
.await?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let result = self
.request(Method::PUT, path)
.header(&CONTENT_LENGTH, "0")
.idempotent(true)
.do_put()
.await?;
let result = self.put(path, PutPayload::new(), Default::default()).await?;

This will help to avoid any potential divergence in future, and also perhaps be a bit more clear what is going on?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've updated to do this, but I also had to update the implementation of self.put to set Content-Length for empty payloads.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put already handles this, this is why we don't see this issue with regular empty non-multipart uploads

Comment thread src/gcp/client.rs Outdated
Comment on lines +400 to +406
let builder = self.request(Method::PUT, path);
let builder = if payload.content_length() == 0 {
builder.header(&CONTENT_LENGTH, "0")
} else {
builder
};
let builder = builder

@tustvold tustvold Mar 21, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change is unnecessary, with_payload already handles this

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah got it, I just reverted this change.

Comment thread src/gcp/client.rs Outdated
Comment on lines 517 to 521
// GCS doesn't allow empty multipart uploads
let result = self
.request(Method::PUT, path)
.idempotent(true)
.do_put()
.put(path, PutPayload::new(), Default::default())
.await?;
self.multipart_cleanup(path, multipart_id).await?;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tustvold I was also trying to think about how to make this more obvious. Perhaps the following structure is more clear? The comment clarifies that we're falling back to a non-multipart upload. Also moving the PUT after the cleanup makes it clear that the cleanup is not affecting the PUT. What do you think?

            // GCS doesn't allow empty multipart uploads, so fallback to regular upload.
            self.multipart_cleanup(path, multipart_id).await?;
            let result = self
                .put(path, PutPayload::new(), Default::default())
                .await?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel strongly either way

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I made the change.

@alamb alamb merged commit 668a876 into apache:main Mar 24, 2025
@alamb

alamb commented Mar 24, 2025

Copy link
Copy Markdown
Contributor

Thank you @jkosh44 and @tustvold -- this is the first PR in the new repo to merge with feature change. So exciting!

@jkosh44 jkosh44 deleted the gcp-multipart-complete branch March 24, 2025 18:46
@jkosh44

jkosh44 commented Mar 24, 2025

Copy link
Copy Markdown
Contributor Author

Thank you for the reviews @alamb and @tustvold!

@andrew-pienso

Copy link
Copy Markdown

Thank you all!

@jkosh44

jkosh44 commented Mar 24, 2025

Copy link
Copy Markdown
Contributor Author

Thank you all!

@andrew-pienso if you still have a repro of #278, it would definitely be helpful to double check that this does in fact resolve your issue.

@andrew-pienso

Copy link
Copy Markdown

Thank you all!

@andrew-pienso if you still have a repro of #278, it would definitely be helpful to double check that this does in fact resolve your issue.

When I filed this bug, this example here would repro. I played around with this for a good half hour and am admittedly not able to repro this anymore in my environment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Generic S3 error: Client error with status 411 Length Required

4 participants