2

I have some (Linux) C code which I am converting to Java. The code has a main loop that checks for a TERM signal from the OS at each looptop and blocks signals otherwise. This is so each "unit of work" it does in the loop is done completely (not interrupted by the TERM signal in the middle).

This has proved somewhat "interesting" to implement in Java. I have come up with some test code (below), which seems to work, but I am unsure if it will always work, or if I've just been "lucky" in my testing.

So, that's my question: is this good code or just code that happens to work sometimes?

TL;DR: work thread and shutdown thread call a common synchronized method

public class TestShutdownHook {
    static int             a    = 0;       /* should end up 0 */
    static volatile int    b    = 0;       /* exit together */
    static boolean         go   = true;    /* signaled to stop */

    /*
     * this simulates a process that we want to do completely
     * or not at all.
     */
    private static void doitall () {
        System.out.println("start");
        ++a;                        /* simulates half the unit of work */
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            System.out.println("exception");  /* doesn't seem to happen */
        }   
        System.out.println("end");
        --a;                        /* the other half */
    }

    /*
     * there can be only one
     */
    private static synchronized void syncit (String msg) {
        if (msg.equals("exit")) go = false;
        if (go) doitall();
    }

    /*
     * starts a thread to wait for a shutdown signal,
     * then goes into the 'while go doit' loop
     */
    public static void main(String[] args) throws InterruptedException {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                int n = 0;  
                System.out.println("Shutdown coming...");
                syncit("exit");  /* can't happen while main is in syncit?  */
                System.out.println("Shutdown hook! " + a);

                /* this isn't really needed, just lets us see "goodbye" */
                while (b == 0) ++n;
                System.out.println("adios..."+n);
            }       
        }); 
        while (go) {
            syncit("loop");
            // there needs to be something else in this loop
            // otherwise, we will starve the shutdown thread.
            // either of the two lines below seem sufficient
            System.out.println("ok");
            Thread.sleep(1);
        }   
        System.out.println("goodbye");
        b = 1;
    }
}
John Hascall
  • 9,176
  • 6
  • 48
  • 72
  • Why do think it wouldn't work every time? I don't see any flaws, on a exit signal the work doitall() should complete if the JVM shuts down normally or with ^C. I guess you have read the Java docs. – Tobias Johansson May 30 '16 at 20:26
  • I did quite a lot of googling on this, and I turned up plenty of talk about JNI and sun.misc.motled.corpse, "you're not thinking the Java way" and what not, but I didn't find this approach, so I was having some self-doubting. – John Hascall May 31 '16 at 00:34

2 Answers2

1

As long as the shut down hook is ran (and completed) in all cases you expect it should be, it must be fine. It seems to work as expected with SIGTERM according to Jarrod Roberson here. If you don't need to catch OS signals explicitly with a signal handler I don't think you need to worry.

Community
  • 1
  • 1
Tobias Johansson
  • 378
  • 4
  • 11
  • Thanks for prodding me to re-read that question -- I think it was [idelvall's](http://stackoverflow.com/a/33778483/2040863) answer which was most useful -- he wanted to smack down all other threads, but it was a short leap from there to asking them close out nicely. – John Hascall Jun 01 '16 at 12:22
1

All available evidence indicates that if you have registered a ShutdownHook, then it will be started when a TERM or INT signal is delivered to the JVM and all other threads will be left alone until the ShutdownHook's run() method exits (when any still running will be terminated).

So, this leads to a simpler solution which avoids synchronized:

public class AnotherTestShutdownHook {
    static int        a          = 0;           /* should end up 0 */
    static boolean    go         = true;        /* signaled to stop */
    static Thread     mainThread = Thread.currentThread();

    private static void trySleep ( int msecs ) {
        try { Thread.sleep(msecs); } catch (InterruptedException e) {}
    }

    private static void tryJoin ( Thread t ) {
        try { t.join(); } catch (InterruptedException e) {}
    }

    /*
     * this simulates a process that we want to do completely
     * or not at all.
     */
    private static void doitall () {
        System.out.println("start");
        ++a;
        trySleep(5000);
        System.out.println("end");
        --a;
    }

    /*
     * starts a thread to wait for a shutdown signal,
     * then does units of work until told to stop
     */
    public static void main(String[] args) {  
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.out.println("Shutdown coming...");
                go = false; 
                tryJoin(mainThread);        // wait for mainThread to exit
                System.out.println("Shutdown hook! " + a);
            }       
        }); 
        while (go) {
            doitall();
        }   
        System.out.println("goodbye");
    }
}
John Hascall
  • 9,176
  • 6
  • 48
  • 72