1

I have an application that is run in a command window. I have a Scanner that detects when the user types /quit, and runs the respective quit() method.

This works perfectly, but is there a way to determine if the user closes the console without entering /quit, and still run quit()?

  • 1
    Add a [shutdown hook](https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#addShutdownHook(java.lang.Thread))? – shmosel Jul 01 '16 at 22:41

2 Answers2

1

Use a shutdown hook. There still isn't a 100% guarantee that it gets called, but it's the best way you have available of trying to catch a shutdown. Generally speaking, you should do the absolute minimum possible in a shutdown hook. Close resources, drop a few logging statements or printstatements, but do nothing that will take long.

I've copied and slightly modified the answer found here Detecting when a Java application closes by Robert to match your code. You should put this code somewhere where you have permissions to call quit(). You only should call this once, but you should call it early in your overall process.

Runtime.getRuntime().addShutdownHook(new Thread() {

    @Override
    public void run() {
        quit();
    }

});

See this question for more details on what a shutdown hook is doing: Useful example of a shutdown hook in Java?

See this question's accepted answer for more details on when a shutdown hook will and won't be called shutdown hook vs finalizer method

See this question for information on how much time you have to work with quit() TL;DR - it depends How long does the JVM allow shutdown hooks to run before calling halt?

Community
  • 1
  • 1
Jeutnarg
  • 1,138
  • 1
  • 16
  • 28
  • Hmm, it appears that when you close the windows command prompt, it closes before doing anything... I put a `Thread.sleep()` in there, and it still closed instantly. I will see if there any other answers before doing anything. Thanks though! =D @Jeutnarg – criticaldiamonds Jul 01 '16 at 23:17
  • Depending on how the OS kills the process it may or may not get a chance to do anything at all. This is why shutdown hooks are not reliable. – Jim Garrison Jul 02 '16 at 00:40
  • @criticaldiamonds your politeness has motivated me - see my other answer – Jeutnarg Jul 03 '16 at 02:19
0

This is the riskier, but more powerful method of catching your program's exit. There are several ways that you could intentionally break this method, and there's some wiggle room where the OS could accidentally break it as well. That's why I said 'riskier'.

You create one class, the Bodyguard, who figures out its own process id and then runs the RealProgram with the processid as input. The Bodyguard then waits until the RealProgram shuts down, then terminates. Meanwhile, the RealProgram watches and checks to see that the Bodyguard's process is still running. If it sees that the Bodyguard has died, then it quits its main loop and calls quit() before terminating.

I have tested this on Windows 10, running Java 8, and killing the Bodyguard from the eclipse debugger. When the Bodyguard is killed, the RealProgram shuts down after calling quit(). When the RealProgram is killed, the Bodyguard terminates as well.

Since I figure you'll want to play around with the code, I've left some of my debugging elements, such as the writer, in there.

I credit:

Bodyguard Class:

package so_38154756;

import java.lang.reflect.InvocationTargetException;

public class Bodyguard {

    public static void main(String[] args){
        Bodyguard lamb = new Bodyguard();
        lamb.initiateSacrifice();
    }

    private void initiateSacrifice() {
        try{
            int ownPid = getPID();
            runRealProgram(ownPid);
        } catch(Exception e){
            System.out.println("Sorry, this isn't working.");
        }
    }

    private int getPID() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        java.lang.management.RuntimeMXBean runtime =  java.lang.management.ManagementFactory.getRuntimeMXBean();
        java.lang.reflect.Field jvm = runtime.getClass().getDeclaredField("jvm");
        jvm.setAccessible(true);
        sun.management.VMManagement mgmt = (sun.management.VMManagement) jvm.get(runtime);
        java.lang.reflect.Method pid_method = mgmt.getClass().getDeclaredMethod("getProcessId");
        pid_method.setAccessible(true);
        int pid = (Integer) pid_method.invoke(mgmt);
        return pid;
    }

    private void runRealProgram(int ownPid) throws Exception {
        String separator = System.getProperty("file.separator");
        String classpath = System.getProperty("java.class.path");
        String path = System.getProperty("java.home") + separator + "bin" + separator + "java";
        ProcessBuilder processBuilder = new ProcessBuilder(path, "-cp", classpath, RealProgram.class.getName(), Integer.valueOf(ownPid).toString());
        Process process = processBuilder.start();
        process.waitFor();//will wait forever, but will close when the RealProgram closes
    }

}

and the RealProgram class:

package so_38154756;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.regex.Pattern;

public class RealProgram {

    private Integer bodyguardPID;
    private PrintWriter writer;

    public RealProgram(Integer bodyguardPID, PrintWriter writer) {
        this.bodyguardPID = bodyguardPID;
        this.writer = writer;
    }

    public static void main(String[] args){
        Integer sacrificialPID = Integer.parseInt(args[0]);
        try {
            PrintWriter writer = new PrintWriter(new File("output.txt"));
            RealProgram rp = new RealProgram(sacrificialPID, writer);
            writer.println("Past Constructor");
            rp.checkToSeeIfBodyguardLives();
            writer.println("Bodyguard had died");
            writer.close();//yes, yes, this could be slightly prettier with Java 8's try-with-resources
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    private void checkToSeeIfBodyguardLives() {
        while(true){
            try { Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}//I wait because I was debugging - you may find it wise to not wait
            //do whatever it is you're doing
            if(!isBodyguardAlive()){
                quit();
                break;
            }
        }
    }

    private boolean isBodyguardAlive() {
        try {
            String process;
            Pattern pattern = Pattern.compile(".*\\\""+bodyguardPID+"\\\".*");//Windows - have tested
            //you'll have to figure out your own different pattern for linux - possible source for editing
            writer.println("Searching for PID of "+bodyguardPID);
            //Process p = Runtime.getRuntime().exec("ps -few"); - Linux - haven't tested - again, possible source for editing
            Process p = Runtime.getRuntime().exec(System.getenv("windir") +"\\system32\\"+"tasklist.exe /fo csv /nh");//Windows - have tested
            BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
            while ((process = input.readLine()) != null) {
                if(pattern.matcher(process).matches()){ 
                    return true;
                }
            }
            input.close();
        } catch (Exception err) {
            err.printStackTrace();
        }
        return false;
    }

    private void quit(){
        writer.println("Detected Bodyguard has died");
    }
}
Community
  • 1
  • 1
Jeutnarg
  • 1,138
  • 1
  • 16
  • 28