Resolve backend DNS on a bounded thread pool to avoid head-of-line blocking#1834
Conversation
|
Since we use java 21, aren't virtual threads a better fit for this? |
|
|
|
Interestingly, we did use Netty's async DNS resolver here in the past: 6e7c029 But this seems to come with some downsides (as the javadoc comment on One nit: ThreadPoolExecutor executor = new ThreadPoolExecutor(
MAX_RESOLVE_THREADS, MAX_RESOLVE_THREADS,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(), // unbounded -> never rejects
new ThreadFactoryBuilder()
.setNameFormat("Velocity DNS Resolver #%d")
.setDaemon(true)
.build());
executor.allowCoreThreadTimeOut(true);Wouldn't throw and has the same queueing behavior as before, just with multiple threads ( |
|
Good catch, pushed a fix. |
|
Netty DNS just turned out to be fairly unreliable for us in the past, worked fine for 99% of people but the 1% who'd have the odd issues with it just wasn't really acceptable; Felt kinda sad because it was fairly nice when it wasn't broken, especially as it defaulted to google DNS or something rather than relying on hosting providers DNS servers |
Yeah that sounds bad actually. If the hosting provider uses split-horizon DNS for backends or you do it manually with docker, you really need to respect the native DNS resolver |
…ocking (PaperMC#1834) * Use a bounded thread pool for backend DNS resolution * Queue DNS lookups instead of rejecting when the resolver pool is busy
Fixes #1833.
SeparatePoolInetNameResolverruns every backend hostname lookup on a single thread (Executors.newSingleThreadExecutor). Because the lookups are blocking, one slow resolve serializes all the others behind it: an unrelated stalled DNS query delays connecting players to otherwise-healthy servers until it returns or the JDK resolver times out. We saw fallback connections to a hostname-defined server hang for 10–18s while the same name resolved instantly on another thread.This swaps the single-thread executor for a small bounded pool (
ThreadPoolExecutor, 0–8 threads, 60s keep-alive,SynchronousQueue), so a slow lookup only ties up one thread instead of blocking all resolution. Threads are created on demand and reaped when idle, so it costs nothing at rest.