1

I've been bit a few times by a java process that wouldn't cooperate and exit cleanly (it would be buried in some low-level libraries out of my control). I am now testing a sort of watchdog that implements a hard stop for the process at some pre-established time, ScheduledStop. That watchdog is a singleton class that runs an independent thread that will kill the whole process if the scheduled stop time arrives. Normally, all threads should return nicely before that hard-stop time and the program exits gracefully. If necessary however, the process kills itself, file locks are released etc. All of this runs on Linux.

I seem to remember that even System.exit(0) is not fool-proof (I think if some shutdown hooks are getting stuck, the process may stay alive), so I have concocted something along the line of:

int pid = MyUtil.getPID();
Runtime.getRuntime().exec(new String[]{"kill", "-9", String.valueOf(pid)});

Now, I'd like to test it with some really un-cooperative threads, and possibly some shutdown hooks that, on purpose for the test, are not doing well.

The itinial NastyThread below is not all that nasty... It ignores InterruptionException, but doesn't prevent System.exit(0). How can I put my VM into a state that even exit() doesn't terminate?

Another question is, although the watchdog thread is in theory independent, what are the conditions where other threads would completely preempt it, thus foiling the scheduled stop?

If necessary, I could launch a separate process (e.g. a simple perl script) that kills the parent (the java process) at some specified time.

/**
 * A Runnable that runs forever and ignores InterruptedException.
 */
private static class NastyThread implements Runnable {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                System.out.println("Received and ignoring "+e);
                System.out.flush();
            }
            System.out.println(ScheduledStop.getInstance().toString());
        }
    }
}
Pierre D
  • 24,012
  • 7
  • 60
  • 96

2 Answers2

0

You can set a SecurityManager to ignore or throw an Error when System.exit() is called.

BTW

while(true) Thread.yield();

or

for(;;);

will ignore interrupts as well.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • mmh, I tried the `forbidSystemExitCall` described here: http://stackoverflow.com/a/5401402/758174 but the application still happily exits with `System.exit(0)`. – Pierre D Sep 16 '13 at 18:02
  • @PierreD Can you check the SecurityManager was in place? – Peter Lawrey Sep 17 '13 at 07:47
0

Ok, I added a SecurityManager as suggested by @Lawrey. Somehow the application can still happily System.exit(0).

But then, I added a shutdown hook that just launches one more nasty thread! That does the trick, and now my ScheduledStop class can be tested (and works). Here is the shutdown hook:

Runtime.getRuntime().addShutdownHook(new Thread(
    new NastyThread("nasty-1 (shutdown-hook)")
));

Here is the output of the test:

gp> ~$ java -cp "$CLASSPATH" com.somepackage.ScheduledStop
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT4.488S)
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT3.939S)
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT3.437S)
main would like to exit(0).
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT2.936S)
nasty-1 (shutdown-hook): Stop scheduled at 20130916-111611-PDT (in PT2.487S)
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT2.434S)
nasty-1 (shutdown-hook): Stop scheduled at 20130916-111611-PDT (in PT1.985S)
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT1.932S)
nasty-1 (shutdown-hook): Stop scheduled at 20130916-111611-PDT (in PT1.484S)
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT1.431S)
nasty-1 (shutdown-hook): Stop scheduled at 20130916-111611-PDT (in PT0.981S)
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT0.928S)
nasty-1 (shutdown-hook): Stop scheduled at 20130916-111611-PDT (in PT0.479S)
nasty-0: Stop scheduled at 20130916-111611-PDT (in PT0.426S)
Hard stop (kill -9 self=6967).
zsh: killed     java -cp "$CLASSPATH" com.somepackage.ScheduledStop

FWIW, here is the whole "test" code (not a real test, just taking ScheduledStop for a little drive around the block):

/*----------------------------------
 * all the code below is for testing
 */
/**
 * A Runnable that runs forever and ignores InterruptedException.
 */
private static class NastyThread implements Runnable {
    private final String name;

public NastyThread(String name) {
    super();
    this.name = name;
}

@Override
public void run() {
    while (true) {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            System.out.println(name+": received and ignoring "+e);
            System.out.flush();
        }
        System.out.println(name+": "+ScheduledStop.getInstance().toString());
    }
}
}

@SuppressWarnings("serial")
private static class ExitTrappedException extends SecurityException { }

private static void forbidSystemExitCall() {
    final SecurityManager securityManager = new SecurityManager() {
        public void checkPermission(Permission permission ) {
            if ("exitVM".equals(permission.getName())) {
                throw new ExitTrappedException() ;
            }
        }
    };
    try {
        System.setSecurityManager(securityManager);
    } catch (Exception e) {
        System.err.println("got: "+e);
    }
}

@SuppressWarnings("unused")
private static void enableSystemExitCall() {
    System.setSecurityManager( null ) ;
}

/**
 * Spawn an un-cooperative thread, then kill itself after a few seconds.
 */
public static void main(String[] args) throws IOException {
    final File lockFile = new File("testStop.lock");
    final Period runFor = Period.seconds(5);
    try (HplFileLock lock = FileUtil.getFileLockOrExit(lockFile, 0)) {
        ScheduledStop.getInstance().runFor(runFor);
    } catch (Exception e) {
        System.err.println("Exception: " + e);
        System.err.flush();
        System.exit(-1);
    }

    // disallow System.exit()
    forbidSystemExitCall();

    // launch a pesky thread that ignores interruption
    Runnable r = new NastyThread("nasty-0");
    new Thread(r).start();

    // further, install a shutdown hook that just launches one more NastyThread!
    Runtime.getRuntime().addShutdownHook(new Thread(new NastyThread("nasty-1 (shutdown-hook)")));

    // now wait 2 seconds and try to exit
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("main would like to exit(0).");
    System.out.flush();
    System.exit(0);
}
Pierre D
  • 24,012
  • 7
  • 60
  • 96