Remove Netty async DNS resolver completely

It "mostly works" - but it's not good enough. Instead, we'll offload
the DNS resolution outside the event loop. This is a middle-ground approach between doing the resolution on the calling method (and potentially a Netty I/O thread) and using the intermittently broken Netty async DNS resolver.
This commit is contained in:
Andrew Steinborn 2020-06-07 00:50:15 -04:00
parent f3d5c986da
commit 6e7c0298de
3 changed files with 88 additions and 79 deletions

View File

@ -6,7 +6,7 @@ import static org.asynchttpclient.Dsl.config;
import com.google.common.base.Preconditions;
import com.velocitypowered.natives.util.Natives;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.network.netty.DnsAddressResolverGroupNameResolverAdapter;
import com.velocitypowered.proxy.network.netty.SeparatePoolInetNameResolver;
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
@ -18,6 +18,7 @@ import io.netty.channel.WriteBufferWaterMark;
import io.netty.channel.epoll.EpollChannelOption;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.resolver.dns.DnsNameResolverBuilder;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
@ -48,7 +49,7 @@ public final class ConnectionManager {
@SuppressWarnings("WeakerAccess")
public final BackendChannelInitializerHolder backendChannelInitializer;
private final DnsAddressResolverGroup resolverGroup;
private final SeparatePoolInetNameResolver resolver;
private final AsyncHttpClient httpClient;
/**
@ -65,21 +66,16 @@ public final class ConnectionManager {
new ServerChannelInitializer(this.server));
this.backendChannelInitializer = new BackendChannelInitializerHolder(
new BackendChannelInitializer(this.server));
this.resolverGroup = new DnsAddressResolverGroup(new DnsNameResolverBuilder()
.channelType(this.transportType.datagramChannelClass)
.negativeTtl(15)
.ndots(1));
this.resolver = new SeparatePoolInetNameResolver(GlobalEventExecutor.INSTANCE);
this.httpClient = asyncHttpClient(config()
.setEventLoopGroup(this.workerGroup)
.setUserAgent(server.getVersion().getName() + "/" + server.getVersion().getVersion())
.addRequestFilter(new RequestFilter() {
@Override
public <T> FilterContext<T> filter(FilterContext<T> ctx) throws FilterException {
public <T> FilterContext<T> filter(FilterContext<T> ctx) {
return new FilterContextBuilder<>(ctx)
.request(new RequestBuilder(ctx.getRequest())
.setNameResolver(
new DnsAddressResolverGroupNameResolverAdapter(resolverGroup, workerGroup)
)
.setNameResolver(resolver)
.build())
.build();
}
@ -162,7 +158,7 @@ public final class ConnectionManager {
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,
this.server.getConfiguration().getConnectTimeout())
.group(group == null ? this.workerGroup : group)
.resolver(this.resolverGroup);
.resolver(this.resolver.asGroup());
if (transportType == TransportType.EPOLL && server.getConfiguration().useTcpFastOpen()) {
bootstrap.option(EpollChannelOption.TCP_FASTOPEN_CONNECT, true);
}
@ -194,6 +190,8 @@ public final class ConnectionManager {
Thread.currentThread().interrupt();
}
}
this.resolver.shutdown();
}
public EventLoopGroup getBossGroup() {

View File

@ -1,68 +0,0 @@
package com.velocitypowered.proxy.network.netty;
import io.netty.channel.EventLoopGroup;
import io.netty.resolver.InetNameResolver;
import io.netty.resolver.dns.DnsAddressResolverGroup;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.ImmediateEventExecutor;
import io.netty.util.concurrent.Promise;
import io.netty.util.internal.ThreadExecutorMap;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
public class DnsAddressResolverGroupNameResolverAdapter extends InetNameResolver {
private final DnsAddressResolverGroup resolverGroup;
private final EventLoopGroup group;
/**
* Creates a DnsAddressResolverGroupNameResolverAdapter.
* @param resolverGroup the resolver group to use
* @param group the event loop group
*/
public DnsAddressResolverGroupNameResolverAdapter(
DnsAddressResolverGroup resolverGroup, EventLoopGroup group) {
super(ImmediateEventExecutor.INSTANCE);
this.resolverGroup = resolverGroup;
this.group = group;
}
@Override
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
EventExecutor executor = this.findExecutor();
resolverGroup.getResolver(executor).resolve(InetSocketAddress.createUnresolved(inetHost, 17))
.addListener((FutureListener<InetSocketAddress>) future -> {
if (future.isSuccess()) {
promise.trySuccess(future.getNow().getAddress());
} else {
promise.tryFailure(future.cause());
}
});
}
@Override
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise)
throws Exception {
EventExecutor executor = this.findExecutor();
resolverGroup.getResolver(executor).resolveAll(InetSocketAddress.createUnresolved(inetHost, 17))
.addListener((FutureListener<List<InetSocketAddress>>) future -> {
if (future.isSuccess()) {
List<InetAddress> addresses = new ArrayList<>(future.getNow().size());
for (InetSocketAddress address : future.getNow()) {
addresses.add(address.getAddress());
}
promise.trySuccess(addresses);
} else {
promise.tryFailure(future.cause());
}
});
}
private EventExecutor findExecutor() {
EventExecutor current = ThreadExecutorMap.currentExecutor();
return current == null ? group.next() : current;
}
}

View File

@ -0,0 +1,79 @@
package com.velocitypowered.proxy.network.netty;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import io.netty.resolver.AddressResolver;
import io.netty.resolver.AddressResolverGroup;
import io.netty.resolver.DefaultNameResolver;
import io.netty.resolver.InetNameResolver;
import io.netty.util.concurrent.EventExecutor;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.RejectedExecutionException;
public final class SeparatePoolInetNameResolver extends InetNameResolver {
private final ExecutorService resolveExecutor;
private final InetNameResolver delegate;
private AddressResolverGroup<InetSocketAddress> resolverGroup;
/**
* Creates a new instnace of {@code SeparatePoolInetNameResolver}.
*
* @param executor the {@link EventExecutor} which is used to notify the listeners of the {@link
* Future} returned by {@link #resolve(String)}
*/
public SeparatePoolInetNameResolver(EventExecutor executor) {
super(executor);
this.resolveExecutor = Executors.newSingleThreadExecutor(
new ThreadFactoryBuilder()
.setNameFormat("Velocity DNS Resolver")
.setDaemon(true)
.build());
this.delegate = new DefaultNameResolver(executor);
}
@Override
protected void doResolve(String inetHost, Promise<InetAddress> promise) throws Exception {
try {
resolveExecutor.execute(() -> this.delegate.resolve(inetHost, promise));
} catch (RejectedExecutionException e) {
promise.setFailure(e);
}
}
@Override
protected void doResolveAll(String inetHost, Promise<List<InetAddress>> promise)
throws Exception {
try {
resolveExecutor.execute(() -> this.delegate.resolveAll(inetHost, promise));
} catch (RejectedExecutionException e) {
promise.setFailure(e);
}
}
public void shutdown() {
this.resolveExecutor.shutdown();
}
/**
* Returns a view of this resolver as a AddressResolverGroup.
*
* @return a view of this resolver as a AddressResolverGroup
*/
public AddressResolverGroup<InetSocketAddress> asGroup() {
if (this.resolverGroup == null) {
this.resolverGroup = new AddressResolverGroup<InetSocketAddress>() {
@Override
protected AddressResolver<InetSocketAddress> newResolver(EventExecutor executor) {
return asAddressResolver();
}
};
}
return this.resolverGroup;
}
}