Skip to content

Add content length to PUT GCP multipart complete #257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 24, 2025

Conversation

jkosh44
Copy link
Contributor

@jkosh44 jkosh44 commented Mar 20, 2025

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.
Copy link
Contributor

@alamb alamb left a comment

Choose a reason for hiding this comment

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

Thank you @jkosh44

@jkosh44
Copy link
Contributor Author

jkosh44 commented Mar 20, 2025

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

@alamb
Copy link
Contributor

alamb commented Mar 21, 2025

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 on lines 518 to 523
let result = self
.request(Method::PUT, path)
.header(&CONTENT_LENGTH, "0")
.idempotent(true)
.do_put()
.await?;
Copy link
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
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
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 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
Copy link
Contributor

@tustvold tustvold Mar 21, 2025

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
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 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
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
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
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
3 checks passed
@alamb
Copy link
Contributor

alamb commented Mar 24, 2025

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
Copy link
Contributor Author

jkosh44 commented Mar 24, 2025

Thank you for the reviews @alamb and @tustvold!

@andrew-pienso
Copy link

Thank you all!

@jkosh44
Copy link
Contributor Author

jkosh44 commented Mar 24, 2025

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

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