From 576bb69230678b20cdc95b48175039303762175d Mon Sep 17 00:00:00 2001 From: asdf2014 Date: Tue, 7 Jul 2026 22:56:31 +0800 Subject: [PATCH] ZOOKEEPER-5044: NettyServerCnxnFactory.shutdown must explicitly shut down DefaultEventExecutor NettyServerCnxnFactory backs its channel group with a dedicated DefaultEventExecutor created as an anonymous instance, so shutdown() cannot terminate it and its non-daemon thread survives every factory shutdown. Instead of adding lifecycle management for that executor, stop creating it: back the channel group with GlobalEventExecutor.INSTANCE, the executor the ChannelGroup javadoc recommends. Its single shared thread starts on demand and stops itself after one second of idleness, so there is nothing left to shut down. This introduces no new thread kind: the termination futures of the boss/worker EventLoopGroups are already notified on GlobalEventExecutor on every shutdown. --- .../apache/zookeeper/server/NettyServerCnxnFactory.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxnFactory.java b/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxnFactory.java index 6f65787442c..5198e91c1d1 100644 --- a/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxnFactory.java +++ b/zookeeper-server/src/main/java/org/apache/zookeeper/server/NettyServerCnxnFactory.java @@ -41,9 +41,9 @@ import io.netty.handler.ssl.SslHandler; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; -import io.netty.util.concurrent.DefaultEventExecutor; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.GlobalEventExecutor; import java.io.IOException; import java.net.InetAddress; import java.net.InetSocketAddress; @@ -107,7 +107,10 @@ public void setOutstandingHandshakeLimit(int limit) { private final ServerBootstrap bootstrap; private Channel parentChannel; - private final ChannelGroup allChannels = new DefaultChannelGroup("zkServerCnxns", new DefaultEventExecutor()); + // GlobalEventExecutor's shared thread starts on demand and stops itself when idle, + // so unlike a dedicated DefaultEventExecutor it needs no explicit shutdown to avoid + // leaking a non-daemon thread on factory shutdown (ZOOKEEPER-5044). + private final ChannelGroup allChannels = new DefaultChannelGroup("zkServerCnxns", GlobalEventExecutor.INSTANCE); private final Map ipMap = new ConcurrentHashMap<>(); private InetSocketAddress localAddress; private int maxClientCnxns = 60;