7

I just stumbled upon a weird behavior of daemon threads which I can't explain. I've reduced my code to a minimal, complete and verifiable sample:

public static void main(String[] args) throws InterruptedException {

    Thread runner = new Thread(() -> {

        final int SIZE = 350_000;
        for (int i = 0; i < SIZE; i++) {
            for (int j = i + 1; j < SIZE; j++) {
                if (i*j == SIZE * SIZE - 1) {
                    return;
                }
            }
        }
    });

    runner.setDaemon(true);
    runner.start();

    // Thread.sleep(1000);
    System.out.println("Exiting.");
}

The code executed by the runner thread takes about 12 secs to terminate on my box, and we're not interested in what it does, since I just needed to spend some time computing.

If this snippet is run as it is, it works as expected: it terminates just after its start. If I uncomment the Thread.sleep(1000) line and run the program, it works for about 12 seconds, then prints out "Exiting" and terminates.

As far as I understood how daemon threads work, I expected this code to run for 1 second and then to terminate execution, since the only user thread running is the one launched with the main() method (the runner is a background daemon thread) and as soon as the 1000 msec are passed, it reaches the end of its execution and the JVM should stop. Also, it looks quite strange that "Exiting" is printed only after 12 seconds, and not when the program starts.

Am I wrong? How can I achieve the desired behavior (pause for a second and then stop, independently from what the runner thread is doing)?

I'm using a 64bit Oracle JDK 1.8.0_112 on a linux box and it has the same behavior either if launched from an IDE or from the command line.

Thanks, Andrea

Andrea Iacono
  • 772
  • 7
  • 20
  • This _definitely_ sounds familiar, but I can't quite remember enough to find the search words that won't just get me a flood of background info on Thread.sleep and deamon threads. :-\ – yshavit Jan 03 '17 at 14:44
  • FWIW, this works as you would expect on my box (windows, jdk 1.8.0_31). Is your box a multi cpu box ? – user2189998 Jan 03 '17 at 14:47
  • 2
    The scheduler can decide to run the daemon thread for 12 seconds before resuming execution of the main thread if it wants. There is nothing prohibiting the behavior you observed. – David Schwartz Jan 03 '17 at 14:47
  • @user2189998 yes, I'm on a Intel i7. – Andrea Iacono Jan 03 '17 at 14:55
  • 2
    @David Schwartz: I agree with you that this behavior is legal, but I *really* hope that any JVM behaves like this (not switching any thread for 12 seconds)! – Andrea Iacono Jan 03 '17 at 14:57
  • This is definitely a bug. I'd open a ticket against Oracle. – John Vint Jan 03 '17 at 15:21
  • I did some initial testing. There are two interesting things to notice. (1) Sleeping the `runner` or busy spinning for 5000 seconds doesn't cause it to fail. It only fails when it uses your program. (2) The JVM cannot hit a safe point, meaning the JVM itself isn't in a recoverable state which explains why it doesn't exit – John Vint Jan 03 '17 at 15:41
  • This looks like JIT optimized out safepoint polling from your loops. Try to run JVM with `-XX:+PrintGCApplicationStoppedTime -XX:+PrintSafepointStatistics -XX:PrintSafepointStatisticsCount=1` to find out is it true. – Alexander Yanyshin Jan 03 '17 at 15:42
  • @DavidSchwartz If you want to be technical, a JVM executing a `main` that just does `System.out.println("hello world");` would be within spec if it pegged all cores at 100% for four years, then printed "hello world." But that's clearly a bug. This bug is less extreme, but similar: the program is taking several magnitudes longer than it reasonably should to complete. – yshavit Jan 03 '17 at 17:09
  • Then the JVM has a bug, see yanys answer. It just doesn't bother to handle this case the way you think it must. (Because it's expensive to do so, most people do not need it to, and it never promised to.) – David Schwartz Jan 03 '17 at 17:10

2 Answers2

5

This is maybe a consequence of counted loop optimization which removed safepoint polls from your nested loops. Try to add -XX:+UseCountedLoopSafepoint flag to your JVM startup options.

Alexander Yanyshin
  • 1,310
  • 10
  • 8
2

Thread#sleep(long) pauses the main thread before it returns from its main method (i.e. before the JVM is considering the program done as long as no non-deamon threads are alive). The scheduler is then free to run any other runnable thread which would be the deamon thread. As it stands, there is no apparent reason for the JVM to forcibly preempt the deamon thread before it finishes execution to continue in the main thread (is it's done sleeping yet), so the JVM is free to continue its schedule. However, it may at any time elect to pause the running thread and schedule another runnable thread for execution, so reproducibility is not guaranteed for your example.

You can force a preemption by inserting calls to Thread#yield() or #sleep(1) in the loops. I bet you'll start seeing the snippet exiting faster and before it finishes the loops.

There's more to learn about Thread states and scheduling, a nice overview can be found here.

Update for comment:

I cannot modify the code in the background thread (is a requirement), so I was looking for a way to stop it if it takes too long (a description of what I'm doing is stackoverflow.com/questions/41226054/… ).

It's legally only possible to stop a running thread from within, so you usually have it test an abort condition every iteration, and if the condition is met, the run method return;s. An abort condition could be as simple as a boolean flag that is set from the outside (! volatile caveat !). So the dead-simplest solution would be to have the main thread set such a flag after the sleep.

Another possibility might be using an ExecutorService that supports timeouts, see this Q&A for an example involving ScheduledExecutorService.

Still I don't understand how the scheduler can decide to wait for 12 seconds before running the System.out instruction.

It does not wait 12 seconds, it let's the deamon thread run to completion because being a deamon only matters to the JVM when deciding if it's safe to halt the JVM. For the scheduler, only the state of the thread matters and as far as it's concernced, after the 1s sleep of the main thread, it has a running (deamon) and a runnable thread (main), and no indication that the running thread should be paused in favor for the runnable thread. Switching threads is also expensive computationally, so the scheduler might be reluctant lacking any indication. An indication to switch might be sleeps and yields, but also GC runs and a whole lot of other things.

Community
  • 1
  • 1
hiergiltdiestfu
  • 2,339
  • 2
  • 25
  • 36
  • I cannot modify the code in the background thread (is a requirement), so I was looking for a way to stop it if it takes too long (a description of what I'm doing is https://stackoverflow.com/questions/41226054/killing-a-java-thread-in-test ). Still I don't understand how the scheduler can decide to wait for 12 seconds before running the System.out instruction.. – Andrea Iacono Jan 03 '17 at 15:07
  • @AndreaIacono I updated my answer accordingly. Does this clear things up? – hiergiltdiestfu Jan 03 '17 at 15:19
  • Using ExecutorService has the same behavior, I've already tried. For the 12 seconds wait: I've used threads for years, and usually the scheduler make them switch after milliseconds or tens of milliseconds; in this case the switch happens after more than 1,000 times later than usual, and this seems strange to me. In fact, I've just tried with JDK 7 and 6, and this does not happen: the System.out is correctly printed after the sleep() and not at the end of the runner thread.But even with those JDKs the daemon thread is kept alive for all its execution, even when the user thread is terminated. – Andrea Iacono Jan 03 '17 at 15:46
  • All the threads in a process must cooperate. If you can't modify an uncooperative thread, you have to isolate it into a separate process. – David Schwartz Jan 03 '17 at 19:32