From 04a9365351f8d5798fa08ccffe06dda08d9ef3af Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 29 Feb 2016 02:42:40 +0100 Subject: [PATCH] Safe shutdown of ThreadGroup for DnsClient --- .../src/dorkbox/network/DnsClient.java | 68 +++++++++++++++---- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/Dorkbox-Network/src/dorkbox/network/DnsClient.java b/Dorkbox-Network/src/dorkbox/network/DnsClient.java index 730d1118..3806d580 100644 --- a/Dorkbox-Network/src/dorkbox/network/DnsClient.java +++ b/Dorkbox-Network/src/dorkbox/network/DnsClient.java @@ -15,6 +15,7 @@ */ package dorkbox.network; +import dorkbox.network.connection.EndPoint; import dorkbox.network.dns.decoder.DomainDecoder; import dorkbox.network.dns.decoder.MailExchangerDecoder; import dorkbox.network.dns.decoder.RecordDecoder; @@ -60,8 +61,10 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.concurrent.TimeUnit; /** * for now, we only support ipv4 @@ -69,6 +72,7 @@ import java.util.Map; @SuppressWarnings("unused") public class DnsClient { + static { try { // doesn't work in eclipse. @@ -118,6 +122,9 @@ class DnsClient { private final DnsNameResolver resolver; private final Map> customDecoders = new HashMap>(); + private ThreadGroup threadGroup; + public static final String THREAD_NAME = "DnsClient"; + private EventLoopGroup eventLoopGroup; /** * Creates a new DNS client, using the provided server (default port 53) for DNS query resolution, with a cache that will obey the TTL of the response @@ -161,38 +168,34 @@ class DnsClient { */ public DnsClient(Collection nameServerAddresses, int minTtl, int maxTtl) { - EventLoopGroup group; Class channelType; - final String threadName = "DnsClient"; - // setup the thread group to easily ID what the following threads belong to (and their spawned threads...) SecurityManager s = System.getSecurityManager(); - ThreadGroup nettyGroup = new ThreadGroup(s != null - ? s.getThreadGroup() - : Thread.currentThread() - .getThreadGroup(), threadName); - nettyGroup.setDaemon(true); + threadGroup = new ThreadGroup(s != null + ? s.getThreadGroup() + : Thread.currentThread() + .getThreadGroup(), THREAD_NAME); + threadGroup.setDaemon(true); if (PlatformDependent.isAndroid()) { // android ONLY supports OIO (not NIO) - group = new OioEventLoopGroup(1, new NamedThreadFactory(threadName + "-UDP", nettyGroup)); + eventLoopGroup = new OioEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-UDP", threadGroup)); channelType = OioDatagramChannel.class; } else if (OS.isLinux()) { // JNI network stack is MUCH faster (but only on linux) - group = new EpollEventLoopGroup(1, new NamedThreadFactory(threadName + "-UDP", nettyGroup)); + eventLoopGroup = new EpollEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-UDP", threadGroup)); channelType = EpollDatagramChannel.class; } else { - group = new NioEventLoopGroup(1, new NamedThreadFactory(threadName + "-UDP", nettyGroup)); + eventLoopGroup = new NioEventLoopGroup(1, new NamedThreadFactory(THREAD_NAME + "-UDP", threadGroup)); channelType = NioDatagramChannel.class; } - DnsNameResolverBuilder builder = new DnsNameResolverBuilder(group.next()); + DnsNameResolverBuilder builder = new DnsNameResolverBuilder(eventLoopGroup.next()); builder.channelFactory(new ReflectiveChannelFactory(channelType)) .channelType(channelType) - .localAddress(null) .ttl(minTtl, maxTtl) .resolvedAddressTypes(InternetProtocolFamily.IPv4) // for now, we only support ipv4 .nameServerAddresses(DnsServerAddresses.sequential(nameServerAddresses)); @@ -335,6 +338,45 @@ class DnsClient { void stop() { reset(); resolver.close(); + + + // we also want to stop the thread group (but NOT in our current thread!) + if (Thread.currentThread() + .getThreadGroup() + .getName() + .equals(THREAD_NAME)) { + + Thread thread = new Thread(new Runnable() { + @Override + public + void run() { + DnsClient.this.stopInThread(); + } + }); + thread.setDaemon(false); + thread.setName("DnsClient Shutdown"); + thread.start(); + } + else { + stopInThread(); + } + } + + + private + void stopInThread() { + // Sometimes there might be "lingering" connections (ie, halfway though registration) that need to be closed. + long maxShutdownWaitTimeInMilliSeconds = EndPoint.maxShutdownWaitTimeInMilliSeconds; + + // we want to WAIT until after the event executors have completed shutting down. + List> shutdownThreadList = new LinkedList>(); + + // now wait it them to finish! + // It can take a few seconds to shut down the executor. This will affect unit testing, where connections are quickly created/stopped + eventLoopGroup.shutdownGracefully(maxShutdownWaitTimeInMilliSeconds, maxShutdownWaitTimeInMilliSeconds * 4, TimeUnit.MILLISECONDS) + .syncUninterruptibly(); + + threadGroup.interrupt(); } public