Skip to content

Commit 4580e26

Browse files
authored
Fix request cancellation logic in the AWS CRT Sync HTTP client (#4887)
* Fix request cancellation logic in the AWS CRT Sync HTTP client * Address feedback
1 parent bab7f24 commit 4580e26

File tree

5 files changed

+118
-1
lines changed

5 files changed

+118
-1
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "AWS CRT Sync HTTP Client",
4+
"contributor": "",
5+
"description": "Fixed an issue where `CancellationException` was thrown incorrectly from AWS CRT Sync HTTP client when execution time exceeded the total configured API call attempt timeout or API call timeout. Now it throws `ApiCallAttemptTimeoutException`/`ApiCallTimeoutException` accordingly. See [#4820](https://github.com/aws/aws-sdk-java-v2/issues/4820)"
6+
}

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClient.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,18 @@ public HttpExecuteResponse call() throws IOException {
129129
throw (HttpException) cause;
130130
}
131131

132+
if (cause instanceof InterruptedException) {
133+
Thread.currentThread().interrupt();
134+
throw new IOException("Request was cancelled", cause);
135+
}
132136
throw new RuntimeException(e.getCause());
133137
}
134138
}
135139

136140
@Override
137141
public void abort() {
138142
if (responseFuture != null) {
139-
responseFuture.cancel(true);
143+
responseFuture.completeExceptionally(new IOException("Request ws cancelled"));
140144
}
141145
}
142146
}

http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientWireMockTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,11 @@
2828

2929
import com.github.tomakehurst.wiremock.junit.WireMockRule;
3030
import java.io.ByteArrayInputStream;
31+
import java.io.IOException;
3132
import java.net.URI;
33+
import java.util.concurrent.Executors;
34+
import java.util.concurrent.ScheduledExecutorService;
35+
import java.util.concurrent.TimeUnit;
3236
import org.junit.AfterClass;
3337
import org.junit.BeforeClass;
3438
import org.junit.Rule;
@@ -51,16 +55,20 @@ public class AwsCrtHttpClientWireMockTest {
5155
public WireMockRule mockServer = new WireMockRule(wireMockConfig()
5256
.dynamicPort());
5357

58+
private static ScheduledExecutorService executorService;
59+
5460
@BeforeClass
5561
public static void setup() {
5662
System.setProperty("aws.crt.debugnative", "true");
5763
Log.initLoggingToStdout(Log.LogLevel.Warn);
64+
executorService = Executors.newScheduledThreadPool(1);
5865
}
5966

6067
@AfterClass
6168
public static void tearDown() {
6269
// Verify there is no resource leak.
6370
CrtResource.waitForNoResources();
71+
executorService.shutdown();
6472
}
6573

6674
@Test
@@ -107,6 +115,26 @@ public void sharedEventLoopGroup_closeOneClient_shouldNotAffectOtherClients() th
107115
}
108116
}
109117

118+
@Test
119+
public void abortRequest_shouldFailTheExceptionWithIOException() throws Exception {
120+
try (SdkHttpClient client = AwsCrtHttpClient.create()) {
121+
String body = randomAlphabetic(10);
122+
URI uri = URI.create("http://localhost:" + mockServer.port());
123+
stubFor(any(urlPathEqualTo("/")).willReturn(aResponse().withFixedDelay(1000).withBody(body)));
124+
SdkHttpRequest request = createRequest(uri);
125+
126+
HttpExecuteRequest.Builder executeRequestBuilder = HttpExecuteRequest.builder();
127+
executeRequestBuilder.request(request)
128+
.contentStreamProvider(() -> new ByteArrayInputStream(new byte[0]));
129+
130+
ExecutableHttpRequest executableRequest = client.prepareRequest(executeRequestBuilder.build());
131+
executorService.schedule(() -> executableRequest.abort(), 100, TimeUnit.MILLISECONDS);
132+
executableRequest.abort();
133+
assertThatThrownBy(() -> executableRequest.call()).isInstanceOf(IOException.class)
134+
.hasMessageContaining("cancelled");
135+
}
136+
}
137+
110138
/**
111139
* Make a simple request and wait for it to finish.
112140
*

test/protocol-tests/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,12 @@
219219
<artifactId>rxjava</artifactId>
220220
<scope>test</scope>
221221
</dependency>
222+
<dependency>
223+
<groupId>software.amazon.awssdk</groupId>
224+
<artifactId>aws-crt-client</artifactId>
225+
<version>${awsjavasdk.version}</version>
226+
<scope>test</scope>
227+
</dependency>
222228
</dependencies>
223229

224230
<build>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
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.protocol.tests.timeout;
17+
18+
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
19+
import static com.github.tomakehurst.wiremock.client.WireMock.any;
20+
import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl;
21+
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
22+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
23+
24+
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
25+
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
26+
import java.net.URI;
27+
import java.time.Duration;
28+
import org.junit.jupiter.api.BeforeEach;
29+
import org.junit.jupiter.api.Test;
30+
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
31+
import software.amazon.awssdk.core.exception.ApiCallAttemptTimeoutException;
32+
import software.amazon.awssdk.core.exception.ApiCallTimeoutException;
33+
import software.amazon.awssdk.http.crt.AwsCrtHttpClient;
34+
import software.amazon.awssdk.regions.Region;
35+
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClient;
36+
import software.amazon.awssdk.services.protocolrestjson.ProtocolRestJsonClientBuilder;
37+
38+
@WireMockTest
39+
public class CrtHttpClientApiCallTimeoutTest {
40+
41+
private ProtocolRestJsonClientBuilder clientBuilder;
42+
43+
@BeforeEach
44+
public void setup(WireMockRuntimeInfo wiremock) {
45+
clientBuilder = ProtocolRestJsonClient.builder()
46+
.region(Region.US_WEST_1)
47+
.endpointOverride(URI.create("http://localhost:" + wiremock.getHttpPort()))
48+
.httpClientBuilder(AwsCrtHttpClient.builder())
49+
.credentialsProvider(() -> AwsBasicCredentials.create("akid", "skid"));
50+
}
51+
52+
@Test
53+
void apiCallAttemptExceeded_shouldThrowApiCallAttemptTimeoutException() {
54+
stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200).withBody("{}").withFixedDelay(2000)));
55+
try (ProtocolRestJsonClient client =
56+
clientBuilder.overrideConfiguration(b -> b.apiCallAttemptTimeout(Duration.ofMillis(10)))
57+
.build()) {
58+
59+
60+
assertThatThrownBy(() -> client.allTypes()).isInstanceOf(ApiCallAttemptTimeoutException.class);
61+
}
62+
}
63+
64+
@Test
65+
void apiCallExceeded_shouldThrowApiCallAttemptTimeoutException() {
66+
stubFor(any(anyUrl()).willReturn(aResponse().withStatus(200).withBody("{}").withFixedDelay(2000)));
67+
try (ProtocolRestJsonClient client = clientBuilder.overrideConfiguration(b -> b.apiCallTimeout(Duration.ofMillis(10)))
68+
.build()) {
69+
70+
assertThatThrownBy(() -> client.allTypes()).isInstanceOf(ApiCallTimeoutException.class);
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)