Skip to content

Commit d85e90f

Browse files
petersv5Peter SvenssonKonicai
authored
use daemon threads to allow shut down of application (#748)
To exit normally all non-daemon threads of the java process are supposed to have stopped. TcpClientSession creates an static class member EventLoopGroup which in turn instantiates worker threads. These are changed to daemon threads to not get in the way of the shutdown. This is similar to the default in Netty 5. To ensure work is finished the Runtime shutdown event is hooked to initiate a time-limited graceful shutdown of the event loops. Normally an application should shut down communication before the process shutdown is inititated. Co-authored-by: Peter Svensson <[email protected]> Co-authored-by: Konicai <[email protected]>
1 parent 2f05a23 commit d85e90f

File tree

2 files changed

+24
-6
lines changed

2 files changed

+24
-6
lines changed

src/main/java/com/github/steveice10/packetlib/tcp/TcpClientSession.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,16 @@
4545
import io.netty.incubator.channel.uring.IOUringSocketChannel;
4646
import io.netty.resolver.dns.DnsNameResolver;
4747
import io.netty.resolver.dns.DnsNameResolverBuilder;
48+
import io.netty.util.concurrent.DefaultThreadFactory;
4849
import java.net.*;
50+
import java.util.concurrent.ThreadFactory;
4951

5052
public class TcpClientSession extends TcpSession {
5153
private static final String IP_REGEX = "\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b";
5254
private static Class<? extends Channel> CHANNEL_CLASS;
5355
private static Class<? extends DatagramChannel> DATAGRAM_CHANNEL_CLASS;
5456
private static EventLoopGroup EVENT_LOOP_GROUP;
57+
private static final int WAIT_FOR_SHUTDOWN_IN_MS = 2000;
5558

5659
private final String bindAddress;
5760
private final int bindPort;
@@ -295,27 +298,37 @@ private static void createTcpEventLoopGroup() {
295298

296299
switch (TransportHelper.determineTransportMethod()) {
297300
case IO_URING:
298-
EVENT_LOOP_GROUP = new IOUringEventLoopGroup();
301+
EVENT_LOOP_GROUP = new IOUringEventLoopGroup(newThreadFactory());
299302
CHANNEL_CLASS = IOUringSocketChannel.class;
300303
DATAGRAM_CHANNEL_CLASS = IOUringDatagramChannel.class;
301304
break;
302305
case EPOLL:
303-
EVENT_LOOP_GROUP = new EpollEventLoopGroup();
306+
EVENT_LOOP_GROUP = new EpollEventLoopGroup(newThreadFactory());
304307
CHANNEL_CLASS = EpollSocketChannel.class;
305308
DATAGRAM_CHANNEL_CLASS = EpollDatagramChannel.class;
306309
break;
307310
case KQUEUE:
308-
EVENT_LOOP_GROUP = new KQueueEventLoopGroup();
311+
EVENT_LOOP_GROUP = new KQueueEventLoopGroup(newThreadFactory());
309312
CHANNEL_CLASS = KQueueSocketChannel.class;
310313
DATAGRAM_CHANNEL_CLASS = KQueueDatagramChannel.class;
311314
break;
312315
case NIO:
313-
EVENT_LOOP_GROUP = new NioEventLoopGroup();
316+
EVENT_LOOP_GROUP = new NioEventLoopGroup(newThreadFactory());
314317
CHANNEL_CLASS = NioSocketChannel.class;
315318
DATAGRAM_CHANNEL_CLASS = NioDatagramChannel.class;
316319
break;
317320
}
318321

319-
Runtime.getRuntime().addShutdownHook(new Thread(() -> EVENT_LOOP_GROUP.shutdownGracefully()));
322+
Runtime.getRuntime().addShutdownHook(new Thread(() -> EVENT_LOOP_GROUP.shutdownGracefully().awaitUninterruptibly(WAIT_FOR_SHUTDOWN_IN_MS)));
323+
}
324+
325+
protected static ThreadFactory newThreadFactory() {
326+
// Create a new daemon thread. When the last non daemon thread ends
327+
// the runtime environment will call the shutdown hooks. One of the
328+
// hooks will try to shut down the event loop group which will
329+
// normally lead to the thread exiting. If not, it will be forcably
330+
// killed after WAIT_FOR_SHUTDOWN_IN_MS ms along with the other
331+
// daemon threads as the runtime exits.
332+
return new DefaultThreadFactory(TcpClientSession.class, true);
320333
}
321334
}

src/main/java/com/github/steveice10/packetlib/tcp/TcpSession.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.netty.handler.timeout.ReadTimeoutHandler;
1616
import io.netty.handler.timeout.WriteTimeoutException;
1717
import io.netty.handler.timeout.WriteTimeoutHandler;
18+
import io.netty.util.concurrent.DefaultThreadFactory;
1819
import net.kyori.adventure.text.Component;
1920

2021
import javax.annotation.Nullable;
@@ -32,6 +33,7 @@ public abstract class TcpSession extends SimpleChannelInboundHandler<Packet> imp
3233
*/
3334
public static boolean USE_EVENT_LOOP_FOR_PACKETS = true;
3435
private static EventLoopGroup PACKET_EVENT_LOOP;
36+
private static final int WAIT_FOR_SHUTDOWN_IN_MS = 2000;
3537

3638
protected String host;
3739
protected int port;
@@ -296,7 +298,10 @@ public void disconnect(final Component reason, final Throwable cause) {
296298
}
297299

298300
if (PACKET_EVENT_LOOP == null) {
299-
PACKET_EVENT_LOOP = new DefaultEventLoopGroup();
301+
// See TcpClientSession.newThreadFactory() for details on
302+
// daemon threads and their interaction with the runtime.
303+
PACKET_EVENT_LOOP = new DefaultEventLoopGroup(new DefaultThreadFactory(this.getClass(), true));
304+
Runtime.getRuntime().addShutdownHook(new Thread(() -> PACKET_EVENT_LOOP.shutdownGracefully().awaitUninterruptibly(WAIT_FOR_SHUTDOWN_IN_MS)));
300305
}
301306
return PACKET_EVENT_LOOP.next();
302307
}

0 commit comments

Comments
 (0)