Skip to content

Commit

Permalink
Now it's possible to configure NettyNioAsyncHttpClient in order to use a
Browse files Browse the repository at this point in the history
non blocking DNS resolver.
  • Loading branch information
martinKindall committed May 31, 2023
1 parent a95b16e commit bac3ae4
Show file tree
Hide file tree
Showing 22 changed files with 928 additions and 219 deletions.
6 changes: 6 additions & 0 deletions .changes/next-release/bugfix-NettyNIOHTTPClient-35595eb.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"category": "Netty NIO HTTP Client",
"contributor": "martinKindall",
"type": "bugfix",
"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."
}
10 changes: 10 additions & 0 deletions bom-internal/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@
<artifactId>netty-buffer</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>org.reactivestreams</groupId>
<artifactId>reactive-streams</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,7 @@ private static Class<?> loadClassViaContext(String fqcn) {
* @throws ClassNotFoundException
* if failed to load the class
*/
public static Class<?> loadClass(String fqcn, Class<?>... classes)
throws ClassNotFoundException {
public static Class<?> loadClass(String fqcn, Class<?>... classes) throws ClassNotFoundException {
return loadClass(fqcn, true, classes);
}

Expand Down
9 changes: 9 additions & 0 deletions http-clients/netty-nio-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@
<groupId>io.netty</groupId>
<artifactId>netty-transport-classes-epoll</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver-dns</artifactId>
<optional>true</optional>
</dependency>

<!--Reactive Dependencies-->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ private NettyNioAsyncHttpClient(DefaultBuilder builder, AttributeMap serviceDefa
.sdkEventLoopGroup(sdkEventLoopGroup)
.sslProvider(resolveSslProvider(builder))
.proxyConfiguration(builder.proxyConfiguration)
.useNonBlockingDnsResolver(builder.useNonBlockingDnsResolver)
.build();
}

Expand Down Expand Up @@ -475,6 +476,15 @@ public interface Builder extends SdkAsyncHttpClient.Builder<NettyNioAsyncHttpCli
* @return the builder for method chaining.
*/
Builder http2Configuration(Consumer<Http2Configuration.Builder> http2ConfigurationBuilderConsumer);

/**
* Configure whether to use a non-blocking dns resolver or not. False by default, as netty's default dns resolver is
* blocking; it namely calls java.net.InetAddress.getByName.
* <p>
* When enabled, a non-blocking dns resolver will be used instead, by modifying netty's bootstrap configuration.
* See https://netty.io/news/2016/05/26/4-1-0-Final.html
*/
Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver);
}

/**
Expand All @@ -492,6 +502,7 @@ private static final class DefaultBuilder implements Builder {
private Http2Configuration http2Configuration;
private SslProvider sslProvider;
private ProxyConfiguration proxyConfiguration;
private Boolean useNonBlockingDnsResolver;

private DefaultBuilder() {
}
Expand Down Expand Up @@ -716,6 +727,16 @@ public void setHttp2Configuration(Http2Configuration http2Configuration) {
http2Configuration(http2Configuration);
}

@Override
public Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
this.useNonBlockingDnsResolver = useNonBlockingDnsResolver;
return this;
}

public void setUseNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
useNonBlockingDnsResolver(useNonBlockingDnsResolver);
}

@Override
public SdkAsyncHttpClient buildWithDefaults(AttributeMap serviceDefaults) {
if (standardOptions.get(SdkHttpConfigurationOption.TLS_NEGOTIATION_TIMEOUT) == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,13 @@
import io.netty.channel.ChannelFactory;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.Optional;
import java.util.concurrent.ThreadFactory;
import software.amazon.awssdk.annotations.SdkPublicApi;
import software.amazon.awssdk.http.nio.netty.internal.utils.SocketChannelResolver;
import software.amazon.awssdk.http.nio.netty.internal.utils.ChannelResolver;
import software.amazon.awssdk.utils.ThreadFactoryBuilder;
import software.amazon.awssdk.utils.Validate;

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

private final EventLoopGroup eventLoopGroup;
private final ChannelFactory<? extends Channel> channelFactory;
private final ChannelFactory<? extends DatagramChannel> datagramChannelFactory;

SdkEventLoopGroup(EventLoopGroup eventLoopGroup, ChannelFactory<? extends Channel> channelFactory) {
Validate.paramNotNull(eventLoopGroup, "eventLoopGroup");
Validate.paramNotNull(channelFactory, "channelFactory");
this.eventLoopGroup = eventLoopGroup;
this.channelFactory = channelFactory;
this.datagramChannelFactory = ChannelResolver.resolveDatagramChannelFactory(eventLoopGroup);
}

/**
* Create an instance of {@link SdkEventLoopGroup} from the builder
*/
private SdkEventLoopGroup(DefaultBuilder builder) {
this.eventLoopGroup = resolveEventLoopGroup(builder);
this.channelFactory = resolveChannelFactory();
this.channelFactory = resolveSocketChannelFactory(builder);
this.datagramChannelFactory = resolveDatagramChannelFactory(builder);
}

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

/**
* @return the {@link ChannelFactory} for datagram channels to be used with Netty Http Client.
*/
public ChannelFactory<? extends DatagramChannel> datagramChannelFactory() {
return datagramChannelFactory;
}

/**
* Creates a new instance of SdkEventLoopGroup with {@link EventLoopGroup} and {@link ChannelFactory}
* to be used with {@link NettyNioAsyncHttpClient}.
Expand All @@ -116,7 +129,7 @@ public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup, ChannelFac
* @return a new instance of SdkEventLoopGroup
*/
public static SdkEventLoopGroup create(EventLoopGroup eventLoopGroup) {
return create(eventLoopGroup, SocketChannelResolver.resolveSocketChannelFactory(eventLoopGroup));
return create(eventLoopGroup, ChannelResolver.resolveSocketChannelFactory(eventLoopGroup));
}

public static Builder builder() {
Expand All @@ -141,11 +154,22 @@ private EventLoopGroup resolveEventLoopGroup(DefaultBuilder builder) {
}*/
}

private ChannelFactory<? extends Channel> resolveChannelFactory() {
// Currently we only support NioEventLoopGroup
private ChannelFactory<? extends Channel> resolveSocketChannelFactory(DefaultBuilder builder) {
return builder.channelFactory;
}

private ChannelFactory<? extends DatagramChannel> resolveDatagramChannelFactory(DefaultBuilder builder) {
return builder.datagramChannelFactory;
}

private static ChannelFactory<? extends Channel> defaultSocketChannelFactory() {
return NioSocketChannel::new;
}

private static ChannelFactory<? extends DatagramChannel> defaultDatagramChannelFactory() {
return NioDatagramChannel::new;
}

/**
* A builder for {@link SdkEventLoopGroup}.
*
Expand All @@ -172,13 +196,33 @@ public interface Builder {
*/
Builder threadFactory(ThreadFactory threadFactory);

/**
* {@link ChannelFactory} to create socket channels used by the {@link EventLoopGroup}. If not set,
* NioSocketChannel is used.
*
* @param channelFactory ChannelFactory to use.
* @return This builder for method chaining.
*/
Builder channelFactory(ChannelFactory<? extends Channel> channelFactory);

/**
* {@link ChannelFactory} to create datagram channels used by the {@link EventLoopGroup}. If not set,
* NioDatagramChannel is used.
*
* @param datagramChannelFactory ChannelFactory to use.
* @return This builder for method chaining.
*/
Builder datagramChannelFactory(ChannelFactory<? extends DatagramChannel> datagramChannelFactory);

SdkEventLoopGroup build();
}

private static final class DefaultBuilder implements Builder {

private Integer numberOfThreads;
private ThreadFactory threadFactory;
private ChannelFactory<? extends Channel> channelFactory = defaultSocketChannelFactory();
private ChannelFactory<? extends DatagramChannel> datagramChannelFactory = defaultDatagramChannelFactory();

private DefaultBuilder() {
}
Expand All @@ -203,6 +247,26 @@ public void setThreadFactory(ThreadFactory threadFactory) {
threadFactory(threadFactory);
}

@Override
public Builder channelFactory(ChannelFactory<? extends Channel> channelFactory) {
this.channelFactory = channelFactory;
return this;
}

public void setChannelFactory(ChannelFactory<? extends Channel> channelFactory) {
channelFactory(channelFactory);
}

@Override
public Builder datagramChannelFactory(ChannelFactory<? extends DatagramChannel> datagramChannelFactory) {
this.datagramChannelFactory = datagramChannelFactory;
return this;
}

public void setDatagramChannelFactory(ChannelFactory<? extends DatagramChannel> datagramChannelFactory) {
datagramChannelFactory(datagramChannelFactory);
}

@Override
public SdkEventLoopGroup build() {
return new SdkEventLoopGroup(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public void channelCreated(Channel ch) throws Exception {
private final ProxyConfiguration proxyConfiguration;
private final BootstrapProvider bootstrapProvider;
private final SslContextProvider sslContextProvider;
private final Boolean useNonBlockingDnsResolver;

private AwaitCloseChannelPoolMap(Builder builder, Function<Builder, BootstrapProvider> createBootStrapProvider) {
this.configuration = builder.configuration;
Expand All @@ -94,6 +95,7 @@ private AwaitCloseChannelPoolMap(Builder builder, Function<Builder, BootstrapPro
this.proxyConfiguration = builder.proxyConfiguration;
this.bootstrapProvider = createBootStrapProvider.apply(builder);
this.sslContextProvider = new SslContextProvider(configuration, protocol, sslProvider);
this.useNonBlockingDnsResolver = builder.useNonBlockingDnsResolver;
}

private AwaitCloseChannelPoolMap(Builder builder) {
Expand Down Expand Up @@ -179,7 +181,7 @@ public void close() {
private Bootstrap createBootstrap(URI poolKey) {
String host = bootstrapHost(poolKey);
int port = bootstrapPort(poolKey);
return bootstrapProvider.createBootstrap(host, port);
return bootstrapProvider.createBootstrap(host, port, useNonBlockingDnsResolver);
}


Expand Down Expand Up @@ -278,6 +280,7 @@ public static class Builder {
private Duration healthCheckPingPeriod;
private SslProvider sslProvider;
private ProxyConfiguration proxyConfiguration;
private Boolean useNonBlockingDnsResolver;

private Builder() {
}
Expand Down Expand Up @@ -327,6 +330,11 @@ public Builder proxyConfiguration(ProxyConfiguration proxyConfiguration) {
return this;
}

public Builder useNonBlockingDnsResolver(Boolean useNonBlockingDnsResolver) {
this.useNonBlockingDnsResolver = useNonBlockingDnsResolver;
return this;
}

public AwaitCloseChannelPoolMap build() {
return new AwaitCloseChannelPoolMap(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,27 @@ public class BootstrapProvider {

/**
* Creates a Bootstrap for a specific host and port with an unresolved InetSocketAddress as the remoteAddress.
* @param host The unresolved remote hostname
* @param port The remote port
* @return A newly created Bootstrap using the configuration this provider was initialized with, and having an
* unresolved remote address.
*
* @param host The unresolved remote hostname
* @param port The remote port
* @param useNonBlockingDnsResolver If true, uses the default non-blocking DNS resolver from Netty. Otherwise, the default
* JDK blocking DNS resolver will be used.
* @return A newly created Bootstrap using the configuration this provider was initialized with, and having an unresolved
* remote address.
*/
public Bootstrap createBootstrap(String host, int port) {
public Bootstrap createBootstrap(String host, int port, Boolean useNonBlockingDnsResolver) {
Bootstrap bootstrap =
new Bootstrap()
.group(sdkEventLoopGroup.eventLoopGroup())
.channelFactory(sdkEventLoopGroup.channelFactory())
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, nettyConfiguration.connectTimeoutMillis())
.option(ChannelOption.SO_KEEPALIVE, nettyConfiguration.tcpKeepAlive())
.remoteAddress(InetSocketAddress.createUnresolved(host, port));

if (Boolean.TRUE.equals(useNonBlockingDnsResolver)) {
bootstrap.resolver(DnsResolverLoader.init(sdkEventLoopGroup.datagramChannelFactory()));
}

sdkChannelOptions.channelOptions().forEach(bootstrap::option);

return bootstrap;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/

package software.amazon.awssdk.http.nio.netty.internal;

import io.netty.channel.ChannelFactory;
import io.netty.channel.socket.DatagramChannel;
import io.netty.resolver.AddressResolverGroup;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import software.amazon.awssdk.annotations.SdkProtectedApi;
import software.amazon.awssdk.utils.ClassLoaderHelper;

/**
* Utility class for instantiating netty dns resolvers only if they're available on the class path.
*/
@SdkProtectedApi
public class DnsResolverLoader {

private DnsResolverLoader() {
}

public static AddressResolverGroup<InetSocketAddress> init(ChannelFactory<? extends DatagramChannel> datagramChannelFactory) {
try {
Class<?> addressResolver = ClassLoaderHelper.loadClass(getAddressResolverGroup(), false, (Class) null);
Class<?> dnsNameResolverBuilder = ClassLoaderHelper.loadClass(getDnsNameResolverBuilder(), false, (Class) null);

Object dnsResolverObj = dnsNameResolverBuilder.newInstance();
Method method = dnsResolverObj.getClass().getMethod("channelFactory", ChannelFactory.class);
method.invoke(dnsResolverObj, datagramChannelFactory);

Object e = addressResolver.getConstructor(dnsNameResolverBuilder).newInstance(dnsResolverObj);
return (AddressResolverGroup<InetSocketAddress>) e;
} catch (ClassNotFoundException e) {
throw new IllegalStateException("Cannot find module io.netty.resolver.dns "
+ " To use netty non blocking dns," +
" the 'netty-resolver-dns' module from io.netty must be on the class path. ", e);
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException | InstantiationException e) {
throw new IllegalStateException("Failed to create AddressResolverGroup", e);
}
}

private static String getAddressResolverGroup() {
return "io.netty.resolver.dns.DnsAddressResolverGroup";
}

private static String getDnsNameResolverBuilder() {
return "io.netty.resolver.dns.DnsNameResolverBuilder";
}
}
Loading

0 comments on commit bac3ae4

Please sign in to comment.