Skip to content

Commit bac3ae4

Browse files
committed
Now it's possible to configure NettyNioAsyncHttpClient in order to use a
non blocking DNS resolver.
1 parent a95b16e commit bac3ae4

22 files changed

+928
-219
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"category": "Netty NIO HTTP Client",
3+
"contributor": "martinKindall",
4+
"type": "bugfix",
5+
"description": "By default, Netty threads are blocked during dns resolution, namely InetAddress.getByName is used under the hood. Now, there's an option to configure the NettyNioAsyncHttpClient in order to use a non blocking dns resolution strategy."
6+
}

bom-internal/pom.xml

+10
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@
134134
<artifactId>netty-buffer</artifactId>
135135
<version>${netty.version}</version>
136136
</dependency>
137+
<dependency>
138+
<groupId>io.netty</groupId>
139+
<artifactId>netty-resolver</artifactId>
140+
<version>${netty.version}</version>
141+
</dependency>
142+
<dependency>
143+
<groupId>io.netty</groupId>
144+
<artifactId>netty-resolver-dns</artifactId>
145+
<version>${netty.version}</version>
146+
</dependency>
137147
<dependency>
138148
<groupId>org.reactivestreams</groupId>
139149
<artifactId>reactive-streams</artifactId>

core/sdk-core/src/main/java/software/amazon/awssdk/core/internal/util/ClassLoaderHelper.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,7 @@ private static Class<?> loadClassViaContext(String fqcn) {
6969
* @throws ClassNotFoundException
7070
* if failed to load the class
7171
*/
72-
public static Class<?> loadClass(String fqcn, Class<?>... classes)
73-
throws ClassNotFoundException {
72+
public static Class<?> loadClass(String fqcn, Class<?>... classes) throws ClassNotFoundException {
7473
return loadClass(fqcn, true, classes);
7574
}
7675

http-clients/netty-nio-client/pom.xml

+9
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@
8585
<groupId>io.netty</groupId>
8686
<artifactId>netty-transport-classes-epoll</artifactId>
8787
</dependency>
88+
<dependency>
89+
<groupId>io.netty</groupId>
90+
<artifactId>netty-resolver</artifactId>
91+
</dependency>
92+
<dependency>
93+
<groupId>io.netty</groupId>
94+
<artifactId>netty-resolver-dns</artifactId>
95+
<optional>true</optional>
96+
</dependency>
8897

8998
<!--Reactive Dependencies-->
9099
<dependency>

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

+21
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefa
103103
.sdkEventLoopGroup(sdkEventLoopGroup)
104104
.sslProvider(resolveSslProvider(builder))
105105
.proxyConfiguration(builder.proxyConfiguration)
106+
.useNonBlockingDnsResolver(builder.useNonBlockingDnsResolver)
106107
.build();
107108
}
108109

@@ -475,6 +476,15 @@ public interface Builder extends SdkAsyncHttpClient.Builder<NettyNioAsyncHttpCli
475476
* @return the builder for method chaining.
476477
*/
477478
Builder http2Configuration(Consumer<Http2Configuration.Builder> http2ConfigurationBuilderConsumer);
479+
480+
/**
481+
* Configure whether to use a non-blocking dns resolver or not. False by default, as netty's default dns resolver is
482+
* blocking; it namely calls java.net.InetAddress.getByName.
483+
* <p>
484+
* When enabled, a non-blocking dns resolver will be used instead, by modifying netty's bootstrap configuration.
485+
* See https://netty.io/news/2016/05/26/4-1-0-Final.html
486+
*/
487+
Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver);
478488
}
479489

480490
/**
@@ -492,6 +502,7 @@ private static final class DefaultBuilder implements Builder {
492502
private Http2Configuration http2Configuration;
493503
private SslProvider sslProvider;
494504
private ProxyConfiguration proxyConfiguration;
505+
private Boolean useNonBlockingDnsResolver;
495506

496507
private DefaultBuilder() {
497508
}
@@ -716,6 +727,16 @@ public void setHttp2Configuration(Http2Configuration http2Configuration) {
716727
http2Configuration(http2Configuration);
717728
}
718729

730+
@Override
731+
public Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
732+
this.useNonBlockingDnsResolver = useNonBlockingDnsResolver;
733+
return this;
734+
}
735+
736+
public void setUseNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
737+
useNonBlockingDnsResolver(useNonBlockingDnsResolver);
738+
}
739+
719740
@Override
720741
public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
721742
if (standardOptions.get(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT) == null) {

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

+70-6
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
import io.netty.channel.ChannelFactory;
2020
import io.netty.channel.EventLoopGroup;
2121
import io.netty.channel.nio.NioEventLoopGroup;
22+
import io.netty.channel.socket.DatagramChannel;
23+
import io.netty.channel.socket.nio.NioDatagramChannel;
2224
import io.netty.channel.socket.nio.NioSocketChannel;
2325
import java.util.Optional;
2426
import java.util.concurrent.ThreadFactory;
2527
import software.amazon.awssdk.annotations.SdkPublicApi;
26-
import software.amazon.awssdk.http.nio.netty.internal.utils.SocketChannelResolver;
28+
import software.amazon.awssdk.http.nio.netty.internal.utils.ChannelResolver;
2729
import software.amazon.awssdk.utils.ThreadFactoryBuilder;
2830
import software.amazon.awssdk.utils.Validate;
2931

@@ -39,7 +41,8 @@
3941
*
4042
* <li>Using {@link #create(EventLoopGroup)} to provide a custom {@link EventLoopGroup}. {@link ChannelFactory} will
4143
* be resolved based on the type of {@link EventLoopGroup} provided via
42-
* {@link SocketChannelResolver#resolveSocketChannelFactory(EventLoopGroup)}
44+
* {@link ChannelResolver#resolveSocketChannelFactory(EventLoopGroup)} and
45+
* {@link ChannelResolver#resolveDatagramChannelFactory(EventLoopGroup)}
4346
* </li>
4447
*
4548
* <li>Using {@link #create(EventLoopGroup, ChannelFactory)} to provide a custom {@link EventLoopGroup} and
@@ -63,20 +66,23 @@ public final class SdkEventLoopGroup {
6366

6467
private final EventLoopGroup eventLoopGroup;
6568
private final ChannelFactory<? extends Channel> channelFactory;
69+
private final ChannelFactory<? extends DatagramChannel> datagramChannelFactory;
6670

6771
SdkEventLoopGroup(EventLoopGroup eventLoopGroup, ChannelFactory<? extends Channel> channelFactory) {
6872
Validate.paramNotNull(eventLoopGroup, "eventLoopGroup");
6973
Validate.paramNotNull(channelFactory, "channelFactory");
7074
this.eventLoopGroup = eventLoopGroup;
7175
this.channelFactory = channelFactory;
76+
this.datagramChannelFactory = ChannelResolver.resolveDatagramChannelFactory(eventLoopGroup);
7277
}
7378

7479
/**
7580
* Create an instance of {@link SdkEventLoopGroup} from the builder
7681
*/
7782
private SdkEventLoopGroup(DefaultBuilder builder) {
7883
this.eventLoopGroup = resolveEventLoopGroup(builder);
79-
this.channelFactory = resolveChannelFactory();
84+
this.channelFactory = resolveSocketChannelFactory(builder);
85+
this.datagramChannelFactory = resolveDatagramChannelFactory(builder);
8086
}
8187

8288
/**
@@ -93,6 +99,13 @@ public ChannelFactory<? extends Channel> channelFactory() {
9399
return channelFactory;
94100
}
95101

102+
/**
103+
* @return the {@link ChannelFactory} for datagram channels to be used with Netty Http Client.
104+
*/
105+
public ChannelFactory<? extends DatagramChannel> datagramChannelFactory() {
106+
return datagramChannelFactory;
107+
}
108+
96109
/**
97110
* Creates a new instance of SdkEventLoopGroup with {@link EventLoopGroup} and {@link ChannelFactory}
98111
* to be used with {@link NettyNioAsyncHttpClient}.
@@ -116,7 +129,7 @@ public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup, ChannelFac
116129
* @return a new instance of SdkEventLoopGroup
117130
*/
118131
public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup) {
119-
return create(eventLoopGroup, SocketChannelResolver.resolveSocketChannelFactory(eventLoopGroup));
132+
return create(eventLoopGroup, ChannelResolver.resolveSocketChannelFactory(eventLoopGroup));
120133
}
121134

122135
public static Builder builder() {
@@ -141,11 +154,22 @@ private EventLoopGroup resolveEventLoopGroup(DefaultBuilder builder) {
141154
}*/
142155
}
143156

144-
private ChannelFactory<? extends Channel> resolveChannelFactory() {
145-
// Currently we only support NioEventLoopGroup
157+
private ChannelFactory<? extends Channel> resolveSocketChannelFactory(DefaultBuilder builder) {
158+
return builder.channelFactory;
159+
}
160+
161+
private ChannelFactory<? extends DatagramChannel> resolveDatagramChannelFactory(DefaultBuilder builder) {
162+
return builder.datagramChannelFactory;
163+
}
164+
165+
private static ChannelFactory<? extends Channel> defaultSocketChannelFactory() {
146166
return NioSocketChannel::new;
147167
}
148168

169+
private static ChannelFactory<? extends DatagramChannel> defaultDatagramChannelFactory() {
170+
return NioDatagramChannel::new;
171+
}
172+
149173
/**
150174
* A builder for {@link SdkEventLoopGroup}.
151175
*
@@ -172,13 +196,33 @@ public interface Builder {
172196
*/
173197
Builder threadFactory(ThreadFactory threadFactory);
174198

199+
/**
200+
* {@link ChannelFactory} to create socket channels used by the {@link EventLoopGroup}. If not set,
201+
* NioSocketChannel is used.
202+
*
203+
* @param channelFactory ChannelFactory to use.
204+
* @return This builder for method chaining.
205+
*/
206+
Builder channelFactory(ChannelFactory<? extends Channel> channelFactory);
207+
208+
/**
209+
* {@link ChannelFactory} to create datagram channels used by the {@link EventLoopGroup}. If not set,
210+
* NioDatagramChannel is used.
211+
*
212+
* @param datagramChannelFactory ChannelFactory to use.
213+
* @return This builder for method chaining.
214+
*/
215+
Builder datagramChannelFactory(ChannelFactory<? extends DatagramChannel> datagramChannelFactory);
216+
175217
SdkEventLoopGroup build();
176218
}
177219

178220
private static final class DefaultBuilder implements Builder {
179221

180222
private Integer numberOfThreads;
181223
private ThreadFactory threadFactory;
224+
private ChannelFactory<? extends Channel> channelFactory = defaultSocketChannelFactory();
225+
private ChannelFactory<? extends DatagramChannel> datagramChannelFactory = defaultDatagramChannelFactory();
182226

183227
private DefaultBuilder() {
184228
}
@@ -203,6 +247,26 @@ public void setThreadFactory(ThreadFactory threadFactory) {
203247
threadFactory(threadFactory);
204248
}
205249

250+
@Override
251+
public Builder channelFactory(ChannelFactory<? extends Channel> channelFactory) {
252+
this.channelFactory = channelFactory;
253+
return this;
254+
}
255+
256+
public void setChannelFactory(ChannelFactory<? extends Channel> channelFactory) {
257+
channelFactory(channelFactory);
258+
}
259+
260+
@Override
261+
public Builder datagramChannelFactory(ChannelFactory<? extends DatagramChannel> datagramChannelFactory) {
262+
this.datagramChannelFactory = datagramChannelFactory;
263+
return this;
264+
}
265+
266+
public void setDatagramChannelFactory(ChannelFactory<? extends DatagramChannel> datagramChannelFactory) {
267+
datagramChannelFactory(datagramChannelFactory);
268+
}
269+
206270
@Override
207271
public SdkEventLoopGroup build() {
208272
return new SdkEventLoopGroup(this);

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

+9-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ public void channelCreated(Channel ch) throws Exception {
8383
private final ProxyConfiguration proxyConfiguration;
8484
private final BootstrapProvider bootstrapProvider;
8585
private final SslContextProvider sslContextProvider;
86+
private final Boolean useNonBlockingDnsResolver;
8687

8788
private AwaitCloseChannelPoolMap(Builder builder, Function<Builder, BootstrapProvider> createBootStrapProvider) {
8889
this.configuration = builder.configuration;
@@ -94,6 +95,7 @@ private AwaitCloseChannelPoolMap(Builder builder, Function<Builder, BootstrapPro
9495
this.proxyConfiguration = builder.proxyConfiguration;
9596
this.bootstrapProvider = createBootStrapProvider.apply(builder);
9697
this.sslContextProvider = new SslContextProvider(configuration, protocol, sslProvider);
98+
this.useNonBlockingDnsResolver = builder.useNonBlockingDnsResolver;
9799
}
98100

99101
private AwaitCloseChannelPoolMap(Builder builder) {
@@ -179,7 +181,7 @@ public void close() {
179181
private Bootstrap createBootstrap(URI poolKey) {
180182
String host = bootstrapHost(poolKey);
181183
int port = bootstrapPort(poolKey);
182-
return bootstrapProvider.createBootstrap(host, port);
184+
return bootstrapProvider.createBootstrap(host, port, useNonBlockingDnsResolver);
183185
}
184186

185187

@@ -278,6 +280,7 @@ public static class Builder {
278280
private Duration healthCheckPingPeriod;
279281
private SslProvider sslProvider;
280282
private ProxyConfiguration proxyConfiguration;
283+
private Boolean useNonBlockingDnsResolver;
281284

282285
private Builder() {
283286
}
@@ -327,6 +330,11 @@ public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) {
327330
return this;
328331
}
329332

333+
public Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
334+
this.useNonBlockingDnsResolver = useNonBlockingDnsResolver;
335+
return this;
336+
}
337+
330338
public AwaitCloseChannelPoolMap build() {
331339
return new AwaitCloseChannelPoolMap(this);
332340
}

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

+13-5
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,27 @@ public class BootstrapProvider {
4343

4444
/**
4545
* Creates a Bootstrap for a specific host and port with an unresolved InetSocketAddress as the remoteAddress.
46-
* @param host The unresolved remote hostname
47-
* @param port The remote port
48-
* @return A newly created Bootstrap using the configuration this provider was initialized with, and having an
49-
* unresolved remote address.
46+
*
47+
* @param host The unresolved remote hostname
48+
* @param port The remote port
49+
* @param useNonBlockingDnsResolver If true, uses the default non-blocking DNS resolver from Netty. Otherwise, the default
50+
* JDK blocking DNS resolver will be used.
51+
* @return A newly created Bootstrap using the configuration this provider was initialized with, and having an unresolved
52+
* remote address.
5053
*/
51-
public Bootstrap createBootstrap(String host, int port) {
54+
public Bootstrap createBootstrap(String host, int port, Boolean useNonBlockingDnsResolver) {
5255
Bootstrap bootstrap =
5356
new Bootstrap()
5457
.group(sdkEventLoopGroup.eventLoopGroup())
5558
.channelFactory(sdkEventLoopGroup.channelFactory())
5659
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyConfiguration.connectTimeoutMillis())
5760
.option(ChannelOption.SO_KEEPALIVE, nettyConfiguration.tcpKeepAlive())
5861
.remoteAddress(InetSocketAddress.createUnresolved(host, port));
62+
63+
if (Boolean.TRUE.equals(useNonBlockingDnsResolver)) {
64+
bootstrap.resolver(DnsResolverLoader.init(sdkEventLoopGroup.datagramChannelFactory()));
65+
}
66+
5967
sdkChannelOptions.channelOptions().forEach(bootstrap::option);
6068

6169
return bootstrap;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.http.nio.netty.internal;
17+
18+
import io.netty.channel.ChannelFactory;
19+
import io.netty.channel.socket.DatagramChannel;
20+
import io.netty.resolver.AddressResolverGroup;
21+
import java.lang.reflect.InvocationTargetException;
22+
import java.lang.reflect.Method;
23+
import java.net.InetSocketAddress;
24+
import software.amazon.awssdk.annotations.SdkProtectedApi;
25+
import software.amazon.awssdk.utils.ClassLoaderHelper;
26+
27+
/**
28+
* Utility class for instantiating netty dns resolvers only if they're available on the class path.
29+
*/
30+
@SdkProtectedApi
31+
public class DnsResolverLoader {
32+
33+
private DnsResolverLoader() {
34+
}
35+
36+
public static AddressResolverGroup<InetSocketAddress> init(ChannelFactory<? extends DatagramChannel> datagramChannelFactory) {
37+
try {
38+
Class<?> addressResolver = ClassLoaderHelper.loadClass(getAddressResolverGroup(), false, (Class) null);
39+
Class<?> dnsNameResolverBuilder = ClassLoaderHelper.loadClass(getDnsNameResolverBuilder(), false, (Class) null);
40+
41+
Object dnsResolverObj = dnsNameResolverBuilder.newInstance();
42+
Method method = dnsResolverObj.getClass().getMethod("channelFactory", ChannelFactory.class);
43+
method.invoke(dnsResolverObj, datagramChannelFactory);
44+
45+
Object e = addressResolver.getConstructor(dnsNameResolverBuilder).newInstance(dnsResolverObj);
46+
return (AddressResolverGroup<InetSocketAddress>) e;
47+
} catch (ClassNotFoundException e) {
48+
throw new IllegalStateException("Cannot find module io.netty.resolver.dns "
49+
+ " To use netty non blocking dns," +
50+
" the 'netty-resolver-dns' module from io.netty must be on the class path. ", e);
51+
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
52+
throw new IllegalStateException("Failed to create AddressResolverGroup", e);
53+
}
54+
}
55+
56+
private static String getAddressResolverGroup() {
57+
return "io.netty.resolver.dns.DnsAddressResolverGroup";
58+
}
59+
60+
private static String getDnsNameResolverBuilder() {
61+
return "io.netty.resolver.dns.DnsNameResolverBuilder";
62+
}
63+
}

0 commit comments

Comments
 (0)