From f67ebac581e05067833491f0bb1672651237e876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Raynaud?= <30211659+carlodrift@users.noreply.github.com> Date: Sun, 28 Jun 2026 19:49:00 +0200 Subject: [PATCH 1/2] Use a bounded thread pool for backend DNS resolution --- .../netty/SeparatePoolInetNameResolver.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java b/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java index 5fc309ae9a..8d0bee291a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java @@ -32,17 +32,20 @@ import java.net.InetSocketAddress; import java.util.List; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * 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 - * and without the downsides of Netty's native DNS resolver. + * on a small bounded pool of separate threads, avoiding blocking the Netty threads for an + * extended period of time and without the downsides of Netty's native DNS resolver. */ public final class SeparatePoolInetNameResolver extends InetNameResolver { + private static final int MAX_RESOLVE_THREADS = 8; + private final ExecutorService resolveExecutor; private final InetNameResolver delegate; private final Cache> cache; @@ -56,9 +59,10 @@ public final class SeparatePoolInetNameResolver extends InetNameResolver { */ public SeparatePoolInetNameResolver(EventExecutor executor) { super(executor); - this.resolveExecutor = Executors.newSingleThreadExecutor( + this.resolveExecutor = new ThreadPoolExecutor(0, MAX_RESOLVE_THREADS, + 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), new ThreadFactoryBuilder() - .setNameFormat("Velocity DNS Resolver") + .setNameFormat("Velocity DNS Resolver #%d") .setDaemon(true) .build()); this.delegate = new DefaultNameResolver(executor); From 5aa2a3f524c261afb93dbe8afbb7214303875c7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Raynaud?= <30211659+carlodrift@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:25:19 +0200 Subject: [PATCH 2/2] Queue DNS lookups instead of rejecting when the resolver pool is busy --- .../network/netty/SeparatePoolInetNameResolver.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java b/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java index 8d0bee291a..fe5e8b29dd 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/network/netty/SeparatePoolInetNameResolver.java @@ -32,8 +32,8 @@ import java.net.InetSocketAddress; import java.util.List; import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.SynchronousQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -59,12 +59,15 @@ public final class SeparatePoolInetNameResolver extends InetNameResolver { */ public SeparatePoolInetNameResolver(EventExecutor executor) { super(executor); - this.resolveExecutor = new ThreadPoolExecutor(0, MAX_RESOLVE_THREADS, - 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), + ThreadPoolExecutor resolveExecutor = new ThreadPoolExecutor( + MAX_RESOLVE_THREADS, MAX_RESOLVE_THREADS, + 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new ThreadFactoryBuilder() .setNameFormat("Velocity DNS Resolver #%d") .setDaemon(true) .build()); + resolveExecutor.allowCoreThreadTimeOut(true); + this.resolveExecutor = resolveExecutor; this.delegate = new DefaultNameResolver(executor); this.cache = Caffeine.newBuilder() .expireAfterWrite(30, TimeUnit.SECONDS)