Resolve backend DNS on a bounded thread pool to avoid head-of-line blocking (#1834)

* Use a bounded thread pool for backend DNS resolution

* Queue DNS lookups instead of rejecting when the resolver pool is busy
This commit is contained in:
Clement Raynaud
2026-07-08 11:27:56 +02:00
committed by GitHub
parent 1edab1411d
commit 81a5817a82
@@ -32,17 +32,20 @@ import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
* An implementation of {@code InetNameResolver} that performs blocking DNS name lookups * An implementation of {@code InetNameResolver} that performs blocking DNS name lookups
* in a separate thread, avoiding blocking the Netty threads for an extended period of time * on a small bounded pool of separate threads, avoiding blocking the Netty threads for an
* and without the downsides of Netty's native DNS resolver. * extended period of time and without the downsides of Netty's native DNS resolver.
*/ */
public final class SeparatePoolInetNameResolver extends InetNameResolver { public final class SeparatePoolInetNameResolver extends InetNameResolver {
private static final int MAX_RESOLVE_THREADS = 8;
private final ExecutorService resolveExecutor; private final ExecutorService resolveExecutor;
private final InetNameResolver delegate; private final InetNameResolver delegate;
private final Cache<String, List<InetAddress>> cache; private final Cache<String, List<InetAddress>> cache;
@@ -56,11 +59,15 @@ public final class SeparatePoolInetNameResolver extends InetNameResolver {
*/ */
public SeparatePoolInetNameResolver(EventExecutor executor) { public SeparatePoolInetNameResolver(EventExecutor executor) {
super(executor); super(executor);
this.resolveExecutor = Executors.newSingleThreadExecutor( ThreadPoolExecutor resolveExecutor = new ThreadPoolExecutor(
MAX_RESOLVE_THREADS, MAX_RESOLVE_THREADS,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(),
new ThreadFactoryBuilder() new ThreadFactoryBuilder()
.setNameFormat("Velocity DNS Resolver") .setNameFormat("Velocity DNS Resolver #%d")
.setDaemon(true) .setDaemon(true)
.build()); .build());
resolveExecutor.allowCoreThreadTimeOut(true);
this.resolveExecutor = resolveExecutor;
this.delegate = new DefaultNameResolver(executor); this.delegate = new DefaultNameResolver(executor);
this.cache = Caffeine.newBuilder() this.cache = Caffeine.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS) .expireAfterWrite(30, TimeUnit.SECONDS)