Skip to content

Now it's possible to configure NettyNioAsyncHttpClient for non blocking DNS #3990

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .brazil.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@
"io.netty:netty-common": { "packageName": "Netty4", "packageVersion": "4.1" },
"io.netty:netty-handler": { "packageName": "Netty4", "packageVersion": "4.1" },
"io.netty:netty-resolver": { "packageName": "Netty4", "packageVersion": "4.1" },
"io.netty:netty-resolver-dns": { "packageName": "Netty4", "packageVersion": "4.1" },
"io.netty:netty-transport": { "packageName": "Netty4", "packageVersion": "4.1" },
"io.netty:netty-transport-classes-epoll": { "packageName": "Netty4", "packageVersion": "4.1" },
"io.netty:netty-transport-native-unix-common": { "packageName": "Netty4", "packageVersion": "4.1" },
Expand Down
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
Loading