question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unstopped threads keep JVM running

See original GitHub issue

It 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:closed
  • Created 5 years ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
0-x-2-2commented, Jun 3, 2018

yeah I have this same issue using the example’s status code

0reactions
regapicturescommented, Jun 3, 2018

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:

Threads before: 
	Thread[main,1,daemon:false]
Version: Spigot 1.12.2, 340
Player Count: 0 / 10
Players: []
Description: §4§k#§r §6Survival §e#2 §r§4§k#
Icon: BufferedImage@159dd606: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@509f5ad1 transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 64 height = 64 #numDataElements 3 dataOff[0] = 2
Server ping took 1004ms
Current thread: Thread[main,5,main]
Threads after: 
	Thread[pool-1-thread-1,15,daemon:false]
Disconnected event: Finished
	Thread[main,1,daemon:false]
Threads: 
	Thread[pool-1-thread-1,15,daemon:false]
Threads: 
	Thread[pool-1-thread-1,15,daemon:false]
Threads: 
Threads: 
Threads: 

Process finished with exit code 0
Read more comments on GitHub >

github_iconTop Results From Across the Web

How Keep JVM Alive Until All Threads Are Completed?
How Keep JVM Alive Until All Threads Are Completed? ... I have a tool running, and is invoked through a BAT file in...
Read more >
Unstopped thread when using Kerberos authentication (with ...
We are maintining a Java Web-application, running with Tomcat 10. After switching to Kerberos-Auth, Tomcat reported a memory leak because a ...
Read more >
Thread behavior in the JVM - Java - InfoWorld
Suspended: The thread is temporarily suspended, and can be resumed by another thread. Blocked: The thread is waiting for an opportunity to run....
Read more >
Managing an OutOfMemory Exception Caused by Thread ...
The most common occurs when Java heap space runs out. There are other flavors like exceeding the garbage collection (GC) overhead limit or...
Read more >
Thread.State (Java Platform SE 7 ) - Oracle Help Center
A thread executing in the Java virtual machine is in this state. BLOCKED A thread that is blocked waiting for a monitor lock...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found