Skip to content
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

Fix for multipart integ test failure #5176

Merged
merged 6 commits into from
May 21, 2024

Conversation

L-Applin
Copy link
Contributor

@L-Applin L-Applin commented May 2, 2024

The S3TransferManagerDownloadPauseResumeIntegrationTest was failing with S3 Multipart client for 2 reasons.

  1. The downloads were not immediately stopped when calling .pause()
  2. When resuming, the range get would have the whole object size as its content-length, making the TransferProgressUpdater report the wrong number of bytes.

This PR is to fix both of those issues.

Motivation and Context

Errors were raised during integration test execution.

Modifications

Fix for the errors consist in

  • Forward cancellation from returnFuture in SplittingTransformer
  • Use content-length for range-get in TransferProgressUpdater, when multipart is enabled.

Testing

Failing integration test run locally.
Added unit test.

- Use content-length for range-get in TransferProgressUpdater, when multipart is enabled.
@L-Applin L-Applin requested a review from a team as a code owner May 2, 2024 17:30
Copy link
Contributor

@dagnir dagnir left a comment

Choose a reason for hiding this comment

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

Can we kick off an integ test on the PR as well?

@@ -196,7 +208,7 @@ private boolean doEmit() {
}

private void handleCancelState() {
synchronized (this) {
synchronized (cancelLock) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What if !onStreanCalled().get() is no longer true after we make the check?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

onStreanCalled will only go from false -> true once and never back to false.

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant more that just because onStreamCalled.get() == false when we evaluate the condition, it doesn't mean it's still false when we execute the body of the if. Is that going to be an issue?

if (!onStreamCalled.get()) { // value is false
     // value could have changed to true after we made the check
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aaah I see what you mean. Looking at it, yep, it might cause a NPE at line 293. Adding synchronization around the if (onStreamCalled.compareAndSet(false, true)) on cancelLock there at line 293 should prevent it.

publisherToUpstream.send(byteBuffer).whenComplete((r, t) -> {
if (t != null) {
handleError(t);
return;
}
subscription.request(1);
if (!isCancelled.get()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Similar question as above, is it possible this is no longer true after we check it? If so, is it an issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

same as above, onStreanCalled will only go from false -> true once and never back to false.

Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the check here assuming subscription is cancelled properly or will be cancelled at this point?
https://github.com/reactive-streams/reactive-streams-jvm

If a Subscription is cancelled its Subscriber MUST eventually stop being signaled.

Note "eventually" not "right away"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we do need to stop right away in the case of the return future being cancelled, for example when pausing with transfer-manager

Copy link
Contributor

Choose a reason for hiding this comment

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

What would happen if we don't check it here and continue to send request demand? It should be no-op right?

@L-Applin
Copy link
Contributor Author

L-Applin commented May 2, 2024

Can we kick off an integ test on the PR as well?

Yep, already did

@@ -118,6 +121,15 @@ private SplittingTransformer(AsyncResponseTransformer<ResponseT, ResultT> upstre
Validate.notNull(maximumBufferSizeInBytes, "maximumBufferSizeInBytes");
this.maximumBufferInBytes = Validate.isPositive(
maximumBufferSizeInBytes, "maximumBufferSizeInBytes");

this.resultFuture.whenComplete((r, e) -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add a test case for this

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for adding a unit test for this. Can we add a end-to-end functional test (wiremock test) to verify the cancellation behavior?

publisherToUpstream.send(byteBuffer).whenComplete((r, t) -> {
if (t != null) {
handleError(t);
return;
}
subscription.request(1);
if (!isCancelled.get()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need the check here assuming subscription is cancelled properly or will be cancelled at this point?
https://github.com/reactive-streams/reactive-streams-jvm

If a Subscription is cancelled its Subscriber MUST eventually stop being signaled.

Note "eventually" not "right away"

@zoewangg
Copy link
Contributor

zoewangg commented May 2, 2024

Rerunning integ tests after fixing parameterized tests. They are passing on my machine now at least. 🤞🏼

Copy link

sonarcloud bot commented May 3, 2024

Quality Gate Failed Quality Gate failed

Failed conditions
75.0% Coverage on New Code (required ≥ 80%)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarCloud

Catch issues before they fail your Quality Gate with our IDE extension SonarLint

publisherToUpstream.send(byteBuffer).whenComplete((r, t) -> {
if (t != null) {
handleError(t);
return;
}
subscription.request(1);
if (!isCancelled.get()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What would happen if we don't check it here and continue to send request demand? It should be no-op right?

@@ -232,14 +267,23 @@ private class IndividualTransformer implements AsyncResponseTransformer<Response
public CompletableFuture<ResponseT> prepare() {
this.individualFuture = new CompletableFuture<>();
if (preparedCalled.compareAndSet(false, true)) {
if (isCancelled.get()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need this logic here?

individualFuture.whenComplete((r, e) -> {
if (isCancelled.get()) {
handleCancelState();
handleSubscriptionCancel();
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you remind me why we need to check isCancelled here for every individual future?

@L-Applin L-Applin merged commit 1660397 into feature/master/s3mpu May 21, 2024
15 of 19 checks passed
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.

None yet

3 participants