Skip to content

Commit 0bef9fd

Browse files
authored
Reuse connections for 5xx errors (#5607)
* Reuse connections for 5xx errors for HTTP/1 requests * Reuse HTTP/2 connections that receive a 5xx error * Fix flaky tests
1 parent 21c9655 commit 0bef9fd

File tree

16 files changed

+33
-106
lines changed

16 files changed

+33
-106
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 HTTP Client",
4+
"contributor": "",
5+
"description": "Reuse connections that receive a 5xx service response."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Netty NIO HTTP Client",
4+
"contributor": "",
5+
"description": "Reuse connections that receive a 5xx service response."
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "bugfix",
3+
"category": "Apache HTTP Client",
4+
"contributor": "",
5+
"description": "Reuse connections that receive a 5xx service response."
6+
}

http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/ApacheHttpClient.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@
7575
import software.amazon.awssdk.http.TlsTrustManagersProvider;
7676
import software.amazon.awssdk.http.apache.internal.ApacheHttpRequestConfig;
7777
import software.amazon.awssdk.http.apache.internal.DefaultConfiguration;
78-
import software.amazon.awssdk.http.apache.internal.SdkConnectionReuseStrategy;
7978
import software.amazon.awssdk.http.apache.internal.SdkProxyRoutePlanner;
8079
import software.amazon.awssdk.http.apache.internal.conn.ClientConnectionManagerFactory;
8180
import software.amazon.awssdk.http.apache.internal.conn.IdleConnectionReaper;
@@ -157,7 +156,6 @@ private ConnectionManagerAwareHttpClient createClient(ApacheHttpClient.DefaultBu
157156
.disableRedirectHandling()
158157
.disableAutomaticRetries()
159158
.setUserAgent("") // SDK will set the user agent header in the pipeline. Don't let Apache waste time
160-
.setConnectionReuseStrategy(new SdkConnectionReuseStrategy())
161159
.setConnectionManager(ClientConnectionManagerFactory.wrap(cm));
162160

163161
addProxyConfig(builder, configuration);

http-clients/apache-client/src/main/java/software/amazon/awssdk/http/apache/internal/SdkConnectionReuseStrategy.java

Lines changed: 0 additions & 49 deletions
This file was deleted.

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/response/CrtResponseAdapter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ private void onSuccessfulResponseComplete(HttpStream stream) {
129129
completionFuture.complete(null);
130130
});
131131

132-
responseHandlerHelper.cleanUpConnectionBasedOnStatusCode(stream);
132+
responseHandlerHelper.releaseConnection(stream);
133133
}
134134

135135
private void handlePublisherError(HttpStream stream, Throwable failure) {

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/response/InputStreamAdaptingHttpStreamResponseHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,6 @@ private void onSuccessfulResponseComplete(HttpStream stream) {
147147

148148
// requestCompletionFuture has been completed at this point, no need to notify the future
149149
simplePublisher.complete();
150-
responseHandlerHelper.cleanUpConnectionBasedOnStatusCode(stream);
150+
responseHandlerHelper.releaseConnection(stream);
151151
}
152152
}

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/response/ResponseHandlerHelper.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import software.amazon.awssdk.crt.http.HttpHeader;
2121
import software.amazon.awssdk.crt.http.HttpHeaderBlock;
2222
import software.amazon.awssdk.crt.http.HttpStream;
23-
import software.amazon.awssdk.http.HttpStatusFamily;
2423
import software.amazon.awssdk.http.SdkHttpResponse;
2524

2625
/**
@@ -88,13 +87,4 @@ public void closeConnection(HttpStream stream) {
8887
}
8988
}
9089
}
91-
92-
public void cleanUpConnectionBasedOnStatusCode(HttpStream stream) {
93-
// always close the connection on a 5XX response code.
94-
if (HttpStatusFamily.of(responseBuilder.statusCode()) == HttpStatusFamily.SERVER_ERROR) {
95-
closeConnection(stream);
96-
} else {
97-
releaseConnection(stream);
98-
}
99-
}
10090
}

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ void serverError_shouldShutdownConnection() {
7777
responseHandler.onResponseHeadersDone(httpStream, 0);
7878
responseHandler.onResponseComplete(httpStream, 0);
7979
requestFuture.join();
80-
verify(crtConn).shutdown();
8180
verify(crtConn).close();
8281
verify(httpStream).close();
8382
}
@@ -121,7 +120,6 @@ void streamClosed_shouldNotIncreaseStreamWindow() throws InterruptedException {
121120

122121
responseHandler.onResponseComplete(httpStream, 0);
123122
requestFuture.join();
124-
verify(crtConn).shutdown();
125123
verify(crtConn).close();
126124
verify(httpStream).close();
127125
verify(httpStream, never()).incrementWindow(anyInt());

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
import org.reactivestreams.Subscriber;
5757
import org.reactivestreams.Subscription;
5858
import software.amazon.awssdk.annotations.SdkInternalApi;
59-
import software.amazon.awssdk.http.HttpStatusFamily;
6059
import software.amazon.awssdk.http.Protocol;
6160
import software.amazon.awssdk.http.SdkCancellationException;
6261
import software.amazon.awssdk.http.SdkHttpFullResponse;
@@ -96,7 +95,7 @@ protected void channelRead0(ChannelHandlerContext channelContext, HttpObject msg
9695
.build();
9796
channelContext.channel().attr(RESPONSE_STATUS_CODE).set(response.status().code());
9897
channelContext.channel().attr(RESPONSE_CONTENT_LENGTH).set(responseContentLength(response));
99-
channelContext.channel().attr(KEEP_ALIVE).set(shouldKeepAlive(response));
98+
channelContext.channel().attr(KEEP_ALIVE).set(HttpUtil.isKeepAlive(response));
10099
ChannelUtils.getAttribute(channelContext.channel(), CHANNEL_DIAGNOSTICS)
101100
.ifPresent(ChannelDiagnostics::incrementResponseCount);
102101
requestContext.handler().onHeaders(sdkResponse);
@@ -203,13 +202,6 @@ private static void finalizeResponse(RequestContext requestContext, ChannelHandl
203202
}
204203
}
205204

206-
private boolean shouldKeepAlive(HttpResponse response) {
207-
if (HttpStatusFamily.of(response.status().code()) == HttpStatusFamily.SERVER_ERROR) {
208-
return false;
209-
}
210-
return HttpUtil.isKeepAlive(response);
211-
}
212-
213205
@Override
214206
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
215207
RequestContext requestContext = ctx.channel().attr(REQUEST_CONTEXT_KEY).get();

http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/http2/Http2ToHttpInboundAdapter.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package software.amazon.awssdk.http.nio.netty.internal.http2;
1717

1818
import io.netty.buffer.ByteBuf;
19-
import io.netty.channel.Channel;
2019
import io.netty.channel.ChannelHandlerContext;
2120
import io.netty.channel.SimpleChannelInboundHandler;
2221
import io.netty.handler.codec.http.DefaultHttpContent;
@@ -32,7 +31,6 @@
3231
import io.netty.handler.codec.http2.HttpConversionUtil;
3332
import java.io.IOException;
3433
import software.amazon.awssdk.annotations.SdkInternalApi;
35-
import software.amazon.awssdk.http.HttpStatusFamily;
3634
import software.amazon.awssdk.http.nio.netty.internal.utils.NettyClientLogger;
3735

3836
/**
@@ -62,20 +60,6 @@ private void onHeadersRead(Http2HeadersFrame headersFrame, ChannelHandlerContext
6260

6361
HttpResponse httpResponse = HttpConversionUtil.toHttpResponse(headersFrame.stream().id(), headersFrame.headers(), true);
6462
ctx.fireChannelRead(httpResponse);
65-
66-
if (HttpStatusFamily.of(httpResponse.status().code()) == HttpStatusFamily.SERVER_ERROR) {
67-
fireConnectionExceptionForServerError(ctx);
68-
}
69-
}
70-
71-
private void fireConnectionExceptionForServerError(ChannelHandlerContext ctx) {
72-
if (ctx.channel().parent() != null) {
73-
Channel parent = ctx.channel().parent();
74-
log.debug(ctx.channel(),
75-
() -> "A 5xx server error occurred on an Http2 stream, notifying the connection channel " + ctx.channel());
76-
parent.pipeline().fireExceptionCaught(new Http2ConnectionTerminatingException("A 5xx server error occurred on an "
77-
+ "Http2 stream " + ctx.channel()));
78-
}
7963
}
8064

8165
private void onDataRead(Http2DataFrame dataFrame, ChannelHandlerContext ctx) throws Http2Exception {

http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/fault/H2ServerErrorTest.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,16 @@ public void teardown() throws InterruptedException {
8888
}
8989

9090
@Test
91-
public void serviceReturn500_newRequestShouldUseNewConnection() {
91+
public void serviceReturn500_newRequestShouldReuseConnection() throws InterruptedException {
9292
server.return500OnFirstRequest = true;
9393
CompletableFuture<?> firstRequest = sendGetRequest(server.port(), netty);
9494
firstRequest.join();
9595

96+
// The request-complete-future does not await the channel-release-future
97+
// Wait a small amount to allow the channel release to complete
98+
Thread.sleep(100);
9699
sendGetRequest(server.port(), netty).join();
97-
assertThat(server.h2ConnectionCount.get()).isEqualTo(2);
100+
assertThat(server.h2ConnectionCount.get()).isEqualTo(1);
98101
}
99102

100103
@Test

http-clients/url-connection-client/src/test/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClientWireMockTest.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@ protected SdkHttpClient createSdkHttpClient(SdkHttpClientOptions options) {
5353
return builder.buildWithDefaults(attributeMap.build());
5454
}
5555

56-
@Override
57-
public void connectionsAreNotReusedOn5xxErrors() {
58-
// We cannot support this because the URL connection client doesn't allow us to disable connection reuse
59-
}
60-
6156
// https://bugs.openjdk.org/browse/JDK-8163921
6257
@Test
6358
public void noAcceptHeader_shouldSet() throws IOException {

http-clients/url-connection-client/src/test/java/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClientWithCustomCreateWireMockTest.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,6 @@ public void testTrustAllWorks() {
6060
public void testCustomTlsTrustManagerAndTrustAllFails() {
6161
}
6262

63-
// Empty test; behavior not supported because the URL connection client does not allow disabling connection reuse
64-
@Override
65-
public void connectionsAreNotReusedOn5xxErrors() throws Exception {
66-
}
67-
6863
@Test
6964
public void testGetResponseCodeNpeIsWrappedAsIo() throws Exception {
7065
connectionInterceptor = safeFunction(connection -> new DelegateHttpURLConnection(connection) {

test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkAsyncHttpClientH1TestSuite.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,16 @@ public void teardown() throws InterruptedException {
8888
}
8989

9090
@Test
91-
public void connectionReceiveServerErrorStatusShouldNotReuseConnection() {
91+
public void connectionReceiveServerErrorStatusShouldReuseConnection() throws InterruptedException {
9292
server.return500OnFirstRequest = true;
9393
server.closeConnection = false;
9494

9595
HttpTestUtils.sendGetRequest(server.port(), client).join();
96+
// The request-complete-future does not await the channel-release-future
97+
// Wait a small amount to allow the channel release to complete
98+
Thread.sleep(100);
9699
HttpTestUtils.sendGetRequest(server.port(), client).join();
97-
assertThat(server.channels.size()).isEqualTo(2);
100+
assertThat(server.channels.size()).isEqualTo(1);
98101
}
99102

100103
@Test

test/http-client-tests/src/main/java/software/amazon/awssdk/http/SdkHttpClientTestSuite.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ public void connectionPoolingWorks() throws Exception {
146146
}
147147

148148
@Test
149-
public void connectionsAreNotReusedOn5xxErrors() throws Exception {
149+
public void connectionsAreReusedOn5xxErrors() throws Exception {
150150
int initialOpenedConnections = CONNECTION_COUNTER.openedConnections();
151151

152152
SdkHttpClientOptions httpClientOptions = new SdkHttpClientOptions();
@@ -169,7 +169,7 @@ public void connectionsAreNotReusedOn5xxErrors() throws Exception {
169169
// connection count increased by at least as many connections as we got 5xx errors back on. But the connection
170170
// manager also predictively creates connections and we need to take those into account in a way that lets it
171171
// remain a dynamic behavior.
172-
assertThat(CONNECTION_COUNTER.openedConnections()).isGreaterThanOrEqualTo(initialOpenedConnections + 5);
172+
assertThat(CONNECTION_COUNTER.openedConnections()).isGreaterThanOrEqualTo(initialOpenedConnections);
173173
}
174174
}
175175

0 commit comments

Comments
 (0)