Unstopped threads keep JVM running
See original GitHub issueIt looks like - after doing a status request to a server - some threads prevent the JVM from exiting.
I’ve been trying it out in a little program of mine. When I do the MCProtocolLib status request in my code, the JVM does not exit, if I don’t do the request (basically call no code of MCProtocolLib), it stops properly. I’m using the example code.
Am I forgetting something? Calling disconnect()
on the Session
does not help (see code below). I know the code above that indicates the session should already be closed. But since I have no clue on why this is happening, I decided to try it anyway.
I hope it’s not something stupid I forgot or missed, I don’t want this to be another waste of your time, I’ve seen a few other reported “issues”.
Got some test code below. Cannot reproduce the hanging in a test; I’m assuming IntelliJ or JUnit forces a stop. So instead, I just get the difference in Threads between before and after the status request, which should yield no difference if everything is cleaned up properly, right? Perhaps thread cleanup is not done properly?
import com.github.steveice10.mc.protocol.MinecraftConstants;
import com.github.steveice10.mc.protocol.MinecraftProtocol;
import com.github.steveice10.mc.protocol.data.SubProtocol;
import com.github.steveice10.mc.protocol.data.status.handler.ServerInfoHandler;
import com.github.steveice10.mc.protocol.data.status.handler.ServerPingTimeHandler;
import com.github.steveice10.packetlib.Client;
import com.github.steveice10.packetlib.tcp.TcpSessionFactory;
import org.junit.Test;
import java.net.Proxy;
import java.util.Arrays;
import java.util.Set;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.assertThat;
public class MinecraftServerControllerTest {
private static final String HOST = "vm-server";
private static final int PORT = 25565;
@Test
public void testStatus() throws InterruptedException {
Set<Thread> activeThreadsBefore = getAllThreads();
printThreads("Threads before: ", activeThreadsBefore);
status();
printThreads("Threads after: ", getAllThreads());
Set<Thread> threadDiff = null;
for (int i = 0; i < 5; i++) {
Thread.sleep(1000);
threadDiff = getAllThreadsMinus(activeThreadsBefore);
printThreads("Threads: ", threadDiff);
}
assertThat(threadDiff, is(empty()));
}
private Set<Thread> getAllThreads() {
return Thread.getAllStackTraces().keySet();
}
private Set<Thread> getAllThreadsMinus(Set<Thread> threadsToSubtract) {
Set<Thread> threads = getAllThreads();
threads.removeAll(threadsToSubtract);
return threads;
}
/*
* When running this as a test in IntelliJ, the JVM exists properly,
* but when running using main(), the JVM does not exit.
*/
private void status() {
// Source:
// https://github.com/Steveice10/MCProtocolLib/blob/master/example/com/github/steveice10/mc/protocol/test/MinecraftProtocolTest.java
MinecraftProtocol protocol = new MinecraftProtocol(SubProtocol.STATUS);
Client client = new Client(HOST, PORT, protocol, new TcpSessionFactory(Proxy.NO_PROXY));
client.getSession().setFlag(MinecraftConstants.AUTH_PROXY_KEY, Proxy.NO_PROXY);
client.getSession().setFlag(MinecraftConstants.SERVER_INFO_HANDLER_KEY, (ServerInfoHandler) (session, info) -> {
System.out.println("Version: " + info.getVersionInfo().getVersionName() + ", " + info.getVersionInfo().getProtocolVersion());
System.out.println("Player Count: " + info.getPlayerInfo().getOnlinePlayers() + " / " + info.getPlayerInfo().getMaxPlayers());
System.out.println("Players: " + Arrays.toString(info.getPlayerInfo().getPlayers()));
System.out.println("Description: " + info.getDescription().getFullText());
System.out.println("Icon: " + info.getIcon());
});
client.getSession().setFlag(MinecraftConstants.SERVER_PING_TIME_HANDLER_KEY,
(ServerPingTimeHandler) (session, pingTime) -> System.out.println("Server ping took " + pingTime + "ms"));
client.getSession().connect();
while(client.getSession().isConnected()) {
try {
Thread.sleep(5);
} catch(InterruptedException e) {
e.printStackTrace();
}
}
// Adding this does not seem to help
client.getSession().disconnect("Bye bye", true);
}
private void printThreads(String prefix, Set<Thread> threads) {
System.out.println(prefix);
threads.stream()
.map(Thread::toString)
.map("\t"::concat)
.forEach(System.out::println);
}
}
The output for me is:
Threads before:
Thread[Monitor Ctrl-Break,5,main]
Thread[Signal Dispatcher,9,system]
Thread[Reference Handler,10,system]
Thread[Finalizer,8,system]
Thread[main,5,main]
Thread[Attach Listener,5,system]
Version: Spigot 1.12.2, 340
Player Count: 0 / 10
Players: []
Description: §4§k#§r §6Survival §e#2 §r§4§k#
Icon: BufferedImage@2e993b02: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@6b9bb0b5 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 64 height = 64 #numDataElements 3 dataOff[0] = 2
Server ping took 1002ms
Disconnected event: Finished
Current thread: Thread[main,5,main]
Threads after:
Thread[Signal Dispatcher,9,system]
Thread[globalEventExecutor-1-1,5,]
Thread[Finalizer,8,system]
Thread[main,5,main]
Thread[ObjectCleanerThread,1,main]
Thread[Attach Listener,5,system]
Thread[Thread-1,5,main]
Thread[Monitor Ctrl-Break,5,main]
Thread[Reference Handler,10,system]
Thread[Java2D Disposer,10,system]
Threads:
Thread[ObjectCleanerThread,1,main]
Thread[Thread-1,5,main]
Thread[Java2D Disposer,10,system]
Threads:
Thread[ObjectCleanerThread,1,main]
Thread[Thread-1,5,main]
Thread[Java2D Disposer,10,system]
Threads:
Thread[ObjectCleanerThread,1,main]
Thread[Thread-1,5,main]
Thread[Java2D Disposer,10,system]
Threads:
Thread[ObjectCleanerThread,1,main]
Thread[Thread-1,5,main]
Thread[Java2D Disposer,10,system]
Threads:
Thread[ObjectCleanerThread,1,main]
Thread[Thread-1,5,main]
Thread[Java2D Disposer,10,system]
java.lang.AssertionError:
Expected: is an empty collection
but: <[Thread[ObjectCleanerThread,1,main], Thread[Thread-1,5,main], Thread[Java2D Disposer,10,system]]>
at org.hamcrest.MatcherAssert.assertThat(MatcherAssert.java:20)
at org.junit.Assert.assertThat(Assert.java:956)
at org.junit.Assert.assertThat(Assert.java:923)
at be.regapictures.autorestarter.controller.MinecraftServerControllerTest.testStatus(MinecraftServerControllerTest.java:51)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Process finished with exit code -1
Thanks for your help in advance!
Issue Analytics
- State:
- Created 5 years ago
- Comments:7 (3 by maintainers)
Top GitHub Comments
yeah I have this same issue using the example’s status code
Good news! Indeed, the JVM now exits properly. Thanks!
Had to filter out daemon threads in my test to get it working there though. There are still some unstopped daemon threads after the status call, but they’re not bothering me.
New output: