diff --git a/.changes/next-release/bugfix-NettyNIOHTTPClient-7b511ce.json b/.changes/next-release/bugfix-NettyNIOHTTPClient-7b511ce.json new file mode 100644 index 000000000000..0fcbfed284ae --- /dev/null +++ b/.changes/next-release/bugfix-NettyNIOHTTPClient-7b511ce.json @@ -0,0 +1,6 @@ +{ + "type": "bugfix", + "category": "Netty NIO HTTP Client", + "contributor": "", + "description": "Fix a bug where, if validation of of the amount of expected data to be received (HTTP `Content-Length`) fails, the connection would be left dangling, consuming a connection from the pool until the client is shut down." +} diff --git a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java index d827fc65657f..cd73ebb7625c 100644 --- a/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java +++ b/http-clients/netty-nio-client/src/main/java/software/amazon/awssdk/http/nio/netty/internal/ResponseHandler.java @@ -376,6 +376,8 @@ public void onComplete() { } } catch (IOException e) { notifyError(e); + runAndLogError(channelContext.channel(), () -> "Could not release channel back to the pool", + () -> closeAndRelease(channelContext)); } } diff --git a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/PublisherAdapterTest.java b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/PublisherAdapterTest.java index f5a417057bd3..4098aec8eac2 100644 --- a/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/PublisherAdapterTest.java +++ b/http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/PublisherAdapterTest.java @@ -235,6 +235,56 @@ public void onComplete() { } } + @Test + public void contentLengthValidationFails_closesAndReleasesConnection() { + channel.attr(ChannelAttributeKey.RESPONSE_CONTENT_LENGTH).set(1L); + channel.attr(ChannelAttributeKey.RESPONSE_DATA_READ).set(0L); + + Publisher publisher = subscriber -> subscriber.onSubscribe(new Subscription() { + @Override + public void request(long l) { + subscriber.onComplete(); + } + + @Override + public void cancel() { + } + }); + + DefaultStreamedHttpResponse streamedResponse = new DefaultStreamedHttpResponse(HttpVersion.HTTP_1_1, + HttpResponseStatus.OK, publisher); + + Subscriber subscriber = new Subscriber() { + private Subscription subscription; + + @Override + public void onSubscribe(Subscription subscription) { + this.subscription = subscription; + subscription.request(Long.MAX_VALUE); + } + + @Override + public void onNext(ByteBuffer byteBuffer) { + } + + @Override + public void onError(Throwable throwable) { + } + + @Override + public void onComplete() { + } + }; + + ResponseHandler.PublisherAdapter publisherAdapter = new ResponseHandler.PublisherAdapter(streamedResponse, ctx, + requestContext, executeFuture); + + publisherAdapter.subscribe(subscriber); + + verify(ctx).close(); + verify(channelPool).release(channel); + } + static final class TestSubscriber implements Subscriber { private Subscription subscription;