Skip to content

Commit 932e15c

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

File tree

21 files changed

+800
-161
lines changed

21 files changed

+800
-161
lines changed
Lines changed: 6 additions & 0 deletions
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

Lines changed: 10 additions & 0 deletions
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

Lines changed: 1 addition & 2 deletions
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

Lines changed: 9 additions & 0 deletions
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

Lines changed: 22 additions & 1 deletion
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

@@ -189,7 +190,7 @@ private Duration resolveHealthCheckPingPeriod(Http2Configuration http2Configurat
189190

190191
private SdkEventLoopGroup nonManagedEventLoopGroup(SdkEventLoopGroup eventLoopGroup) {
191192
return SdkEventLoopGroup.create(new NonManagedEventLoopGroup(eventLoopGroup.eventLoopGroup()),
192-
eventLoopGroup.channelFactory());
193+
eventLoopGroup.channelFactory(), eventLoopGroup.channelType());
193194
}
194195

195196
@Override
@@ -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

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,20 @@
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

3032
/**
31-
* Provides {@link EventLoopGroup} and {@link ChannelFactory} for {@link NettyNioAsyncHttpClient}.
33+
* Provides {@link EventLoopGroup}, {@link ChannelFactory} and {@link DatagramChannel} for {@link NettyNioAsyncHttpClient}.
3234
* <p>
33-
* There are three ways to create a new instance.
35+
* There are four ways to create a new instance.
3436
*
3537
* <ul>
3638
* <li>using {@link #builder()} to provide custom configuration of {@link EventLoopGroup}.
@@ -39,11 +41,15 @@
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)}. {@link DatagramChannel} will be resolved based
45+
* on the type of {@link EventLoopGroup} provided via {@link ChannelResolver#resolveDatagramChannel(EventLoopGroup)}
4346
* </li>
4447
*
45-
* <li>Using {@link #create(EventLoopGroup, ChannelFactory)} to provide a custom {@link EventLoopGroup} and
46-
* {@link ChannelFactory}
48+
* <li>Using {@link #create(EventLoopGroup, ChannelFactory)} to provide a custom
49+
* {@link EventLoopGroup} and {@link ChannelFactory}</li>
50+
*
51+
* <li>Using {@link #create(EventLoopGroup, ChannelFactory, Class)} to provide a custom
52+
* {@link EventLoopGroup}, {@link ChannelFactory} and {@link DatagramChannel}
4753
* </ul>
4854
*
4955
* <p>
@@ -63,12 +69,23 @@ public final class SdkEventLoopGroup {
6369

6470
private final EventLoopGroup eventLoopGroup;
6571
private final ChannelFactory<? extends Channel> channelFactory;
72+
private final Class<? extends DatagramChannel> channelType;
6673

6774
SdkEventLoopGroup(EventLoopGroup eventLoopGroup, ChannelFactory<? extends Channel> channelFactory) {
6875
Validate.paramNotNull(eventLoopGroup, "eventLoopGroup");
6976
Validate.paramNotNull(channelFactory, "channelFactory");
7077
this.eventLoopGroup = eventLoopGroup;
7178
this.channelFactory = channelFactory;
79+
this.channelType = resolveChannelType();
80+
}
81+
82+
SdkEventLoopGroup(EventLoopGroup eventLoopGroup, ChannelFactory<? extends Channel> channelFactory,
83+
Class<? extends DatagramChannel> channelType) {
84+
Validate.paramNotNull(eventLoopGroup, "eventLoopGroup");
85+
Validate.paramNotNull(channelFactory, "channelFactory");
86+
this.eventLoopGroup = eventLoopGroup;
87+
this.channelFactory = channelFactory;
88+
this.channelType = channelType;
7289
}
7390

7491
/**
@@ -77,6 +94,7 @@ public final class SdkEventLoopGroup {
7794
private SdkEventLoopGroup(DefaultBuilder builder) {
7895
this.eventLoopGroup = resolveEventLoopGroup(builder);
7996
this.channelFactory = resolveChannelFactory();
97+
this.channelType = resolveChannelType();
8098
}
8199

82100
/**
@@ -93,6 +111,13 @@ public ChannelFactory<? extends Channel> channelFactory() {
93111
return channelFactory;
94112
}
95113

114+
/**
115+
* @return the {@link ChannelFactory} to be used with Netty Http Client.
116+
*/
117+
public Class<? extends DatagramChannel> channelType() {
118+
return channelType;
119+
}
120+
96121
/**
97122
* Creates a new instance of SdkEventLoopGroup with {@link EventLoopGroup} and {@link ChannelFactory}
98123
* to be used with {@link NettyNioAsyncHttpClient}.
@@ -105,6 +130,20 @@ public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup, ChannelFac
105130
return new SdkEventLoopGroup(eventLoopGroup, channelFactory);
106131
}
107132

133+
/**
134+
* Creates a new instance of SdkEventLoopGroup with {@link EventLoopGroup}, {@link ChannelFactory}
135+
* and {@link DatagramChannel} to be used with {@link NettyNioAsyncHttpClient}.
136+
*
137+
* @param eventLoopGroup the EventLoopGroup to be used
138+
* @param channelFactory the channel factor to be used
139+
* @param channelType the channel type to be used
140+
* @return a new instance of SdkEventLoopGroup
141+
*/
142+
public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup, ChannelFactory<? extends Channel> channelFactory,
143+
Class<? extends DatagramChannel> channelType) {
144+
return new SdkEventLoopGroup(eventLoopGroup, channelFactory, channelType);
145+
}
146+
108147
/**
109148
* Creates a new instance of SdkEventLoopGroup with {@link EventLoopGroup}.
110149
*
@@ -116,7 +155,8 @@ public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup, ChannelFac
116155
* @return a new instance of SdkEventLoopGroup
117156
*/
118157
public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup) {
119-
return create(eventLoopGroup, SocketChannelResolver.resolveSocketChannelFactory(eventLoopGroup));
158+
return create(eventLoopGroup, ChannelResolver.resolveSocketChannelFactory(eventLoopGroup),
159+
ChannelResolver.resolveDatagramChannel(eventLoopGroup));
120160
}
121161

122162
public static Builder builder() {
@@ -146,6 +186,10 @@ private ChannelFactory<? extends Channel> resolveChannelFactory() {
146186
return NioSocketChannel::new;
147187
}
148188

189+
private Class<? extends DatagramChannel> resolveChannelType() {
190+
return NioDatagramChannel.class;
191+
}
192+
149193
/**
150194
* A builder for {@link SdkEventLoopGroup}.
151195
*

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

Lines changed: 9 additions & 1 deletion
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

Lines changed: 13 additions & 5 deletions
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 (useNonBlockingDnsResolver) {
64+
bootstrap.resolver(DnsResolverLoader.init(sdkEventLoopGroup.channelType()));
65+
}
66+
5967
sdkChannelOptions.channelOptions().forEach(bootstrap::option);
6068

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ public static synchronized SdkEventLoopGroup get() {
5959

6060
referenceCount++;
6161
return SdkEventLoopGroup.create(new ReferenceCountingEventLoopGroup(sharedSdkEventLoopGroup.eventLoopGroup()),
62-
sharedSdkEventLoopGroup.channelFactory());
62+
sharedSdkEventLoopGroup.channelFactory(), sharedSdkEventLoopGroup.channelType());
6363
}
6464

6565
/**

0 commit comments

Comments
 (0)