2

I use the code below , in my jframe in order to execute a shell script via java program, but when i implement an other method to stop runing process, this method is blocked by th first and i can't stop my program. So i msut kill the new process, the problem is how to get the pid of the process and how to put it in background, in order to kill it.

public static void execShellCmd(String cmd) {
    try {

        testPath = getPath();

        System.out.println(cmd);
        Runtime runtime = Runtime.getRuntime();
        process = runtime.exec(new String[] { "sh",
                testPath + "/install.sh", cmd, "&" });
        int exitValue = process.waitFor();
        value = exitValue;
        System.out.println("exit value: " + exitValue);
        BufferedReader buf = new BufferedReader(new InputStreamReader(
                process.getInputStream()));
        String line = "";
        BufferedWriter bufferedWriter = null;
        while ((line = buf.readLine()) != null) {
            System.out.println("exec response: " + line);
            log = line;
            writeToFile(line);
        }
    } catch (Exception e) {
        System.out.println(e);
    }
}
Andrii Abramov
  • 10,019
  • 9
  • 74
  • 96
user2043602
  • 237
  • 2
  • 4
  • 15

2 Answers2

2

If your "other method" has access to the Process object returned by the original exec then you can simply call process.destroy() to kill the running process. You don't need to know the native PID. Here's a more-or-less complete example using SwingWorker (there may be a few try/catch blocks I've forgotten):

private Process runningProcess = null;

public static void execShellCmd(String cmd) {
    if(runningProcess != null) {
        // print some suitable warning about process already running
        return;
    }
    try {

        testPath = getPath();

        System.out.println(cmd);
        Runtime runtime = Runtime.getRuntime();
        runningProcess = runtime.exec(new String[] { "sh",
                testPath + "/install.sh", cmd });

        new SwingWorker<Integer, Void>() {
            protected Integer doInBackground() {
                // read process output first
                BufferedReader buf = new BufferedReader(new InputStreamReader(
                    process.getInputStream()));
                String line = "";
                while ((line = buf.readLine()) != null) {
                    System.out.println("exec response: " + line);
                    log = line;
                    writeToFile(line);
                }

                // only once we've run out of output can we call waitFor
                while(true) {
                    try {
                        return runningProcess.waitFor();
                    } catch(InterruptedException e) {
                        // do nothing, wait again
                    } finally {
                        Thread.interrupted();
                    }
                }
                return -1;
            }

            protected void done() {
                runningProcess = null;
            }
        }.execute();
    }
}

public static void stopProcess() {
    if(runningProcess != null) {
        runningProcess.destroy();
    }
}

As long as execShellCmd and stopProcess are only called from the event dispatch thread then no synchronization is necessary, as all accesses to the runningProcess field happen on the same thread (the point of SwingWorker is that doInBackground happens on a worker thread but done is run on the EDT).

Ian Roberts
  • 120,891
  • 16
  • 170
  • 183
  • I implemented an other method calling process. destroy but once the method implemented to run the script is running. i can't run an other method. and i have no access.i must wait the end of the execution of the shell script. So my idea is to put the execution in a thread and run it in background and the destroy in other. – user2043602 Feb 27 '13 at 09:52
  • You mention that this is called from a JFrame, so yes, the `waitFor` call should definitely not be done on the event handler thread, it needs to go on a background thread (e.g. using a SwingWorker). – Ian Roberts Feb 27 '13 at 09:56
  • i have 2 jbutton. one to run and other to stop. when i run the program i can't stop it – user2043602 Feb 27 '13 at 10:01
  • @user2043602 I've added a SwingWorker example, does that help? – Ian Roberts Feb 27 '13 at 10:27
  • @user2043602 also note that I've _deliberately_ removed the `&` from the end of your `runtime.exec` command line - you don't need to background the process at the shell level because external processes are always asynchronous with respect to java. I suspect the `&` may have been one of the main causes of your previous problems... – Ian Roberts Feb 27 '13 at 10:56
  • i made th change that you heva recomended but, i still have the some problem – user2043602 Feb 27 '13 at 12:15
2

For this, you can employ the features present in java.util.concurrent

In your existing class containing your UI, you have to add something similar to this:

//class variable to store the future of your task
private Future<?> taskFuture = null;

//to be called from button "Start" action handler
public void actionStart() {

  //don't double start, if there is one already running
  if(taskFuture == null || taskFuture.isDone()) {

    //create the new runnable instance, with the proper commands to execute
    MyShellExecutor ex = new MyShellExecutor(new String[] { "sh",testPath + "/install.sh", cmd, "&" });

    //we only need one additional Thread now, but this part can be tailored to fit different needs
    ExecutorService newThreadExecutor = Executors.newSingleThreadExecutor();

    //start the execution of the task, which will start execution of the shell command
    taskFuture = newThreadExecutor.submit(ex);
  }
}

//to be called from button "Stop" action handler
public void actionStop() {
  //if not already done, or cancelled, cancel it
  if(taskFuture !=null && !taskFuture.isDone()) {
    taskFuture.cancel(true);
  }
}

The main component doing the job is the Runnable which I named MyShellExecutor, and looks like this:

public class MyShellExecutor implements Runnable {

    //stores the command to be executed
    private final String[] toExecute;

    public MyShellExecutor(String[] toExecute) {
        this.toExecute=toExecute;
    }

    public void run() {
        Runtime runtime = Runtime.getRuntime();
        Process process = null;

        try {
            process = runtime.exec(toExecute);

            int exitValue = process.waitFor();
            System.out.println("exit value: " + exitValue);
            BufferedReader buf = new BufferedReader(new InputStreamReader(process.getInputStream()));

            String line = "";
            while ((line = buf.readLine()) != null) {
                System.out.println("exec response: " + line);
                //do whatever you need to do
            }

        } catch (InterruptedException e) {
            //thread was interrupted.
            if(process!=null) { process.destroy(); }
            //reset interrupted flag
            Thread.currentThread().interrupt();

        } catch (Exception e) {
            //an other error occurred
            if(process!=null) { process.destroy(); }
        }

    }
}

Note: when running time consuming operations, be sure not to do it on the UI thread. That just blocks the user, and doesn't provide a nice user experience. Always do anzthing that might make the user wait on a different Thread.

Recommended reading: Java Concurrency In Practice

ppeterka
  • 20,583
  • 6
  • 63
  • 78
  • thank you. i was looking at the same problem as the op today and adapted the proposed solution. nice, clean and does what it should do. – Lutz Müller May 25 '16 at 18:48