Skip to content

Commit 6b4dace

Browse files
authored
Retry on InternalError error codes. (#2963)
This fixes an issue where 200s followed by a failure were not retried for S3.
1 parent b673b84 commit 6b4dace

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Amazon S3",
3+
"contributor": "",
4+
"type": "bugfix",
5+
"description": "Retry on InternalError error code, which fixes an issue where 200s followed by a failure were not retried."
6+
}

core/aws-core/src/main/java/software/amazon/awssdk/awscore/internal/AwsErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ public final class AwsErrorCode {
6464
retryableErrorCodes.add("PriorRequestNotComplete");
6565
retryableErrorCodes.add("RequestTimeout");
6666
retryableErrorCodes.add("RequestTimeoutException");
67+
retryableErrorCodes.add("InternalError");
6768
RETRYABLE_ERROR_CODES = unmodifiableSet(retryableErrorCodes);
6869
}
6970

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.services.s3.functionaltests;
17+
18+
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
19+
import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
20+
import static com.github.tomakehurst.wiremock.client.WireMock.put;
21+
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
22+
import static org.assertj.core.api.Assertions.assertThat;
23+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
24+
25+
import com.github.tomakehurst.wiremock.junit.WireMockRule;
26+
import java.net.URI;
27+
import org.junit.Rule;
28+
import org.junit.Test;
29+
import software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider;
30+
import software.amazon.awssdk.core.interceptor.Context;
31+
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
32+
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
33+
import software.amazon.awssdk.core.retry.RetryMode;
34+
import software.amazon.awssdk.core.retry.RetryPolicy;
35+
import software.amazon.awssdk.regions.Region;
36+
import software.amazon.awssdk.services.s3.S3Client;
37+
import software.amazon.awssdk.services.s3.model.S3Exception;
38+
39+
public class RetriesOn200Test {
40+
public static final String ERROR_CODE = "InternalError";
41+
public static final String ERROR_MESSAGE = "We encountered an internal error. Please try again.";
42+
public static final String ERROR_BODY = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
43+
+ "<Error>\n"
44+
+ " <Code>" + ERROR_CODE + "</Code>\n"
45+
+ " <Message>" + ERROR_MESSAGE + "</Message>\n"
46+
+ "</Error>";
47+
48+
@Rule
49+
public WireMockRule mockServer = new WireMockRule(0);
50+
51+
@Test
52+
public void copyObjectRetriesOn200InternalErrorFailures() {
53+
AttemptCountingInterceptor countingInterceptor = new AttemptCountingInterceptor();
54+
S3Client client = S3Client.builder()
55+
.endpointOverride(URI.create("http://localhost:" + mockServer.port()))
56+
.region(Region.US_WEST_2)
57+
.credentialsProvider(AnonymousCredentialsProvider.create())
58+
.overrideConfiguration(c -> c.retryPolicy(RetryMode.STANDARD)
59+
.addExecutionInterceptor(countingInterceptor))
60+
.build();
61+
62+
63+
stubFor(put(anyUrl())
64+
.willReturn(aResponse().withStatus(200)
65+
.withBody(ERROR_BODY)));
66+
67+
assertThatThrownBy(() -> client.copyObject(r -> r.sourceBucket("foo").sourceKey("foo")
68+
.destinationBucket("bar").destinationKey("bar")))
69+
.isInstanceOfSatisfying(S3Exception.class, e -> {
70+
assertThat(e.statusCode()).isEqualTo(200);
71+
assertThat(e.awsErrorDetails().errorCode()).isEqualTo(ERROR_CODE);
72+
assertThat(e.awsErrorDetails().errorMessage()).isEqualTo(ERROR_MESSAGE);
73+
});
74+
assertThat(countingInterceptor.attemptCount).isEqualTo(RetryPolicy.forRetryMode(RetryMode.STANDARD).numRetries() + 1);
75+
}
76+
77+
private static final class AttemptCountingInterceptor implements ExecutionInterceptor {
78+
private long attemptCount = 0;
79+
80+
@Override
81+
public void beforeTransmission(Context.BeforeTransmission context, ExecutionAttributes executionAttributes) {
82+
++attemptCount;
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)