Skip to content

Commit f0fef84

Browse files
committed
Propagate exceptions properly when netty nio http client protocol configuration fails
1 parent e15a534 commit f0fef84

File tree

4 files changed

+67
-16
lines changed

4 files changed

+67
-16
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"category": "Netty NIO Http Client",
3+
"type": "bugfix",
4+
"description": "Propagate exception properly when an exception is thrown from protocol initialization."
5+
}

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

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,18 +127,26 @@ private void failProtocolImplPromise(Throwable e) {
127127
});
128128
}
129129

130-
void completeProtocolConfiguration(Channel newChannel, Protocol protocol) {
130+
private void completeProtocolConfiguration(Channel newChannel, Protocol protocol) {
131131
doInEventLoop(eventLoop, () -> {
132132
if (closed) {
133-
newChannel.close();
134-
delegatePool.release(newChannel);
135-
protocolImplPromise.setFailure(new IllegalStateException("Pool closed"));
133+
closeAndRelease(newChannel, new IllegalStateException("Pool closed"));
136134
} else {
137-
protocolImplPromise.setSuccess(configureProtocol(newChannel, protocol));
135+
try {
136+
protocolImplPromise.setSuccess(configureProtocol(newChannel, protocol));
137+
} catch (Throwable e) {
138+
closeAndRelease(newChannel, e);
139+
}
138140
}
139141
});
140142
}
141143

144+
private void closeAndRelease(Channel newChannel, Throwable e) {
145+
newChannel.close();
146+
delegatePool.release(newChannel);
147+
protocolImplPromise.setFailure(e);
148+
}
149+
142150
private ChannelPool configureProtocol(Channel newChannel, Protocol protocol) {
143151
if (Protocol.HTTP1_1 == protocol) {
144152
// For HTTP/1.1 we use a traditional channel pool without multiplexing

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,16 @@ public void overrideConnectionIdleTimeout_shouldHonor() {
129129
}
130130
}
131131

132+
@Test
133+
public void invalidMaxPendingConnectionAcquireConfig_shouldPropagateException() {
134+
try (SdkAsyncHttpClient customClient = NettyNioAsyncHttpClient.builder()
135+
.maxConcurrency(1)
136+
.maxPendingConnectionAcquires(0)
137+
.build()) {
138+
assertThatThrownBy(() -> makeSimpleRequest(customClient)).hasMessageContaining("java.lang.IllegalArgumentException: maxPendingAcquires: 0 (expected: >= 1)");
139+
}
140+
}
141+
132142
@Test
133143
public void customFactoryIsUsed() throws Exception {
134144
ThreadFactory threadFactory = spy(new CustomThreadFactory());

http-clients/netty-nio-client/src/test/java/software/amazon/awssdk/http/nio/netty/internal/http2/HttpOrHttp2ChannelPoolTest.java

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,23 @@
1515

1616
package software.amazon.awssdk.http.nio.netty.internal.http2;
1717

18+
import static org.assertj.core.api.Assertions.assertThat;
19+
import static org.mockito.Matchers.eq;
20+
import static org.mockito.Mockito.verify;
21+
import static org.mockito.Mockito.when;
22+
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT;
23+
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.MAX_PENDING_CONNECTION_ACQUIRES;
24+
import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.PROTOCOL_FUTURE;
25+
1826
import io.netty.channel.Channel;
1927
import io.netty.channel.EventLoopGroup;
2028
import io.netty.channel.nio.NioEventLoopGroup;
2129
import io.netty.channel.pool.ChannelPool;
2230
import io.netty.channel.socket.nio.NioSocketChannel;
2331
import io.netty.util.concurrent.Future;
2432
import io.netty.util.concurrent.Promise;
33+
import java.time.Duration;
34+
import java.util.concurrent.CompletableFuture;
2535
import org.junit.AfterClass;
2636
import org.junit.Before;
2737
import org.junit.BeforeClass;
@@ -30,20 +40,10 @@
3040
import org.mockito.Mock;
3141
import org.mockito.runners.MockitoJUnitRunner;
3242
import software.amazon.awssdk.http.Protocol;
43+
import software.amazon.awssdk.http.nio.netty.internal.MockChannel;
3344
import software.amazon.awssdk.http.nio.netty.internal.NettyConfiguration;
3445
import software.amazon.awssdk.utils.AttributeMap;
3546

36-
import java.time.Duration;
37-
import java.util.concurrent.CompletableFuture;
38-
39-
import static org.assertj.core.api.Assertions.assertThat;
40-
import static org.mockito.Matchers.eq;
41-
import static org.mockito.Mockito.verify;
42-
import static org.mockito.Mockito.when;
43-
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.CONNECTION_ACQUIRE_TIMEOUT;
44-
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.MAX_PENDING_CONNECTION_ACQUIRES;
45-
import static software.amazon.awssdk.http.nio.netty.internal.ChannelAttributeKey.PROTOCOL_FUTURE;
46-
4747
/**
4848
* Tests for {@link HttpOrHttp2ChannelPool}.
4949
*/
@@ -82,6 +82,34 @@ public void protocolConfigNotStarted_closeSucceeds() {
8282
httpOrHttp2ChannelPool.close();
8383
}
8484

85+
@Test(timeout = 5_000)
86+
public void invalidProtocolConfig_shouldFailPromise() throws Exception {
87+
HttpOrHttp2ChannelPool invalidChannelPool = new HttpOrHttp2ChannelPool(mockDelegatePool,
88+
eventLoopGroup,
89+
4,
90+
new NettyConfiguration(AttributeMap.builder()
91+
.put(CONNECTION_ACQUIRE_TIMEOUT, Duration.ofSeconds(1))
92+
.put(MAX_PENDING_CONNECTION_ACQUIRES, 0)
93+
.build()));
94+
95+
Promise<Channel> acquirePromise = eventLoopGroup.next().newPromise();
96+
when(mockDelegatePool.acquire()).thenReturn(acquirePromise);
97+
98+
Thread.sleep(500);
99+
100+
Channel channel = new MockChannel();
101+
eventLoopGroup.register(channel);
102+
103+
channel.attr(PROTOCOL_FUTURE).set(CompletableFuture.completedFuture(Protocol.HTTP1_1));
104+
105+
acquirePromise.setSuccess(channel);
106+
107+
Future<Channel> p = invalidChannelPool.acquire();
108+
assertThat(p.await().cause().getMessage()).contains("maxPendingAcquires: 0 (expected: >= 1)");
109+
verify(mockDelegatePool).release(channel);
110+
assertThat(channel.isOpen()).isFalse();
111+
}
112+
85113
@Test
86114
public void protocolConfigNotStarted_closeClosesDelegatePool() throws InterruptedException {
87115
httpOrHttp2ChannelPool.close();

0 commit comments

Comments
 (0)