2

I've been scratching my head trying to figure out a hang issue with Java Timers. I was wondering if someone here could help out. Any help in diagnosing the issue is highly appreciated.

I have a simple program with three TimerTask classes (A, B, and Stopper). A and B run repeatedly every 400ms and 500ms respectively. Stopper task is scheduled to run at 2sec to shutdown everything. The timers fire as expected, and the run() methods of tasks execute as expected. However, once the stopper task executes, I expect the program to terminate, but it just hangs after printing "All tasks and timers canceled, exiting". I've tried using jstack to diagnose the problem but there is nothing obvious that indicates what, if anything needs to be released/stopped/canceled etc.

Here is my code:

package com.example.experiments;

import java.util.Date;

/** 
 * A test timer class to check behavior of exit/hang issues
 */
public class TimerTest {

    TimerTest(){
    }

    class TaskA extends java.util.TimerTask {

        TaskA(){
        }
        public void run() {
            System.err.println("A.run() called.");

            if (!running){
                System.err.println("A: calling this.cancel().");
                this.cancel();
                return;
            }

        }
        public boolean cancel(){
            System.err.println("Canceling TaskA");
            return super.cancel();
        }
    }

    class TaskB extends java.util.TimerTask {

        TaskB(){
        }

        public void run(){
            System.err.println("B.run() called.");

            if (!running){
                System.err.println("B: calling this.cancel().");
                this.cancel();
                return;
            }

        }
        public boolean cancel(){
            System.err.println("Canceling TaskB");
            return super.cancel();
        }
    }


    private void start(){
        this.running = true; // Flag to indicate if the server loop should continue running or not

        final java.util.Timer timerA = new java.util.Timer();
        final TaskA taskA = new TaskA();
        timerA.schedule(taskA, 0, 400);

        final java.util.Timer timerB = new java.util.Timer();
        final TaskB taskB = new TaskB();
        timerB.schedule(taskB, 0, 500);

        class StopperTask extends java.util.TimerTask {
            private java.util.Timer myTimer;

            StopperTask(java.util.Timer timer){
                myTimer = timer;
            }

            public void run(){
                taskA.cancel();
                taskB.cancel();
                timerA.cancel();
                timerB.cancel();

                this.cancel();
                myTimer.cancel();
                System.err.println("Stopper task completed");
            }
        }
        final java.util.Timer stopperTimer = new java.util.Timer();
        final StopperTask stopperTask = new StopperTask(stopperTimer);
        stopperTimer.schedule(stopperTask, 2*1000);


        /** Register witjh JVM to be notified on when the JVM is about to exit */
        java.lang.Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.err.println("shutting down...");
                running = false;

                taskA.cancel();
                taskB.cancel();
                timerA.cancel();
                timerB.cancel();

                stopperTask.cancel();
                stopperTimer.cancel();

                System.err.println("All tasks and timers canceled, exiting");
                System.exit(0);
            }
        });     

    }

    public static void main(String[] args) {
        new TimerTest().start();
    }

    private boolean running = false;
}
PKK
  • 753
  • 2
  • 7
  • 18
  • Just a comment out of curiousity, why are you not importing all classes but referring to them with their full names? You clearly seem to have the capability as you have an import. – skiwi Jul 23 '13 at 18:54
  • "Everything is going as expected, but the program just hangs when the Stopper task gets executed", this statement is confusing. Stopper task always issue (or) at some point of time? – kosa Jul 23 '13 at 18:55
  • Add the condition to test if process is running and then cancel it.. see if it hangs. – Phani Jul 23 '13 at 19:02
  • @skiwi: No reason really, pasted from some existing code someone had written – PKK Jul 23 '13 at 19:03
  • @Nambari: Clarified description, please let me know if it's still unclear – PKK Jul 23 '13 at 19:03

2 Answers2

6

As Karthik answered, remove the System.exit(0) and the program won't hang. I also agree with his remark about volatile keyword.

When the shutdown hooks are being ran, the JVM is already in its shutdown sequence which is guarded by a "static" monitor. Invoking the System.exit(0) method at that time will effectively put the JVM in a deadlock state.

Consider the following code example:

public static void main(String[] args) {
    System.out.println(Thread.currentThread().getName());
    java.lang.Runtime.getRuntime().addShutdownHook(new Thread() {
        @Override
        public void run() {   
            System.out.println(Thread.currentThread().getName());
            System.exit(0);                
        }
    });  
}

It will also hang - the red square button means that the program is still running and as you can see in the console tab it printed out the name of the thread that ran the main method (main) and the name of the thread that runs the shutdown hook (Thread-0):

Eclipse IDE screenshot

When you call the System.exit method, the method which will in turn be called is the Shutdown.exit method (I have omitted all the irrelevant source):

static void exit(int status) {

   ...

    synchronized (Shutdown.class) {  // "static" monitor mentioned in the first part of the post        
        sequence();
        halt(status);
    }
}

The sequence method runs all hooks and finalizers, while the halt method invokes the native halt0 method at which point the JVM finally exits, I suppose.

So this is what happens:

  • the main method is ran in the main thread, it prints the thread name and registers the shutdown hook
  • since there is no other code in it, the main thread dies
  • The DestroyJavaVM thread is started to execute the shutdown of the JVM
  • The DestroyJavaVM thread enters the synchronized block in the Shutdown.exit method and acquires the Shutdown.class monitor
  • The sequence method runs the registered shutdown hooks
  • The Thread-0 thread is started to run our shutdown hook registered in the main method
  • The Thread-0 thread prints its name and initiates another JVM shutdown via the System.exit method, which in turn tries to acquire the Shutdown.class monitor but it can't since it is already acquired

To sum up:

  • the DestroyJavaVM thread waits for the Thread-0 thread to finish
  • the Thread-0 thread waits for the DestroyJavaVM thread to finish

which is deadlock by definition.

Notes:

  • For additional info I recommend reading SO question How to capture System.exit event?
  • The linked code for system java classes is openjdk 6-b14 while mine is oracle 1.6.0_37 but I noticed no difference in the source.
  • I think that Eclipse doesn't show the thread states right, Thread-0 should definitely be in BLOCKED state since it tried to acquire a taken monitor (see here for code example). Not sure about the DestroyJavaVM thread, I won't assume without doing a thread dump.
Community
  • 1
  • 1
linski
  • 5,046
  • 3
  • 22
  • 35
0

Instead of System.exit(0) perform a return. Also, you should be marking your running variable volatile.

kosa
  • 65,990
  • 13
  • 130
  • 167