In short, not with Java ≤ 8, but it is possible with Java 9 and java.lang.ProcessHandle
.
A Process
in Java is not meant to model an arbitrary OS process, it actually models a sub-process of the java process, so you can't attach to another running process, and expect to get all features a Process
has:
The class Process provides methods for performing input from the process, performing output to the process, waiting for the process to complete, checking the exit status of the process, and destroying (killing) the process.
As you see, input and output requires your java process to have a handle on the other process's stdin/stdout/stderr, which only makes sense for a child process. The doc also mentions "the subprocess" everywhere, thus implying it's a child process.
In Java 9 the doc explicitly starts with:
Process provides control of native processes started by ProcessBuilder.start
and Runtime.exec
.
But Java 9 also brings ProcessHandle
, which is exactly what you need:
ProcessHandle
identifies and provides control of native processes. Each individual process can be monitored for liveness, list its children, get information about the process or destroy it. By comparison, Process
instances were started by the current process and additionally provide access to the process input, output, and error streams.
So, if you can run your tests with Java 9, you can try using ProcessHandle.allProcesses()
to find the process you want, and then kill it with .destroy()
, monitor its liveness with isAlive()
, or use onExit()
.
Optional<ProcessHandle> optional = ProcessHandle.allProcesses().filter(process -> {
Optional<String> command = process.info().command();
return command.isPresent() && command.get().equals("Chromedriver.exe");
}).findFirst();
if (optional.isPresent()) {
ProcessHandle processHandle = optional.get();
System.out.println("Killing process " + processHandle.pid());
processHandle.destroy();
try {
System.out.println("Waiting for process " + processHandle.pid() + " to exit...");
processHandle.onExit().get();
System.out.println("Done !");
} catch (InterruptedException|ExecutionException e) {
e.printStackTrace();
}
}
That being said, if you really want to, you can create a subclass of Process
by yourself, that will provide limited functionality (ie nothing about I/O, only waitFor
and destroy
& such).
Below is an awful example quickly hacked using ideas from this Q&A for the Windows implementation.
- find process by name using
tasklist /FO CSV /FI "IMAGENAME eq Chromedriver.exe", to get the PID
- kill it with
taskkill
- wait for it to end with a polling-loop that uses
tasklist
/FO CSV /FI "PID eq ..."
The Linux version uses pgrep
, kill
, and /proc/{pid}/
.
Please note it's not really useful to wrap that as an actual instance of java.lang.Process
, so I've separated the concerns with a tool class that implements the required features for windows, and a subclass of Process
implemented as an exercise to show what's possible, and what is not possible (details in code). So if you need that, better use ProcessTool
directly, not DummyProcess
.
(I've tested this on another process, I can't speak for Chromedriver.exe
, you might need to adapt a few details)
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Paths;
import java.util.Scanner;
public class WaitForProcessDemo {
public static void main(String[] args) throws IOException, InterruptedException {
Process p = getDummyProcess("Chromedriver");
System.out.println("Killing...");
p.destroy();
System.out.println("Waiting...");
p.waitFor();
System.out.println("Done.");
}
private static Process getDummyProcess(String exeName) throws IOException {
ProcessTool tool = new WindowsProcessTool();
long pid = tool.findProcessByName(exeName);
return new DummyProcess(pid, tool);
}
interface ProcessTool {
long findProcessByName(String exeName) throws IOException;
void killProcess(long pid) throws IOException;
boolean isPidStillThere(long pid);
}
private static class WindowsProcessTool implements ProcessTool {
@Override
public long findProcessByName(String exeName) throws IOException {
String processInfoCSV = findProcessInfoCSV("IMAGENAME eq " + exeName);
String[] fields = processInfoCSV.split("\"");
String pid = fields[3];
return Integer.parseInt(pid);
}
String findProcessInfoCSV(String filter) throws IOException {
Process p = new ProcessBuilder("tasklist", "-FO", "CSV", "/FI", filter)
.redirectErrorStream(true)
.start();
Scanner scanner = new Scanner(p.getInputStream());
scanner.nextLine(); // skip header line
if (scanner.hasNextLine()) {
return scanner.nextLine();
}
throw new IOException("No such process: " + filter);
}
@Override
public void killProcess(long pid) throws IOException {
new ProcessBuilder("taskkill", "/PID", String.valueOf(pid))
.redirectErrorStream(true)
.start();
}
@Override
public boolean isPidStillThere(long pid) {
try {
findProcessInfoCSV("PID eq " + pid);
return true;
} catch (IOException e) {
return false;
}
}
}
private static class LinuxProcessTool implements ProcessTool {
@Override
public long findProcessByName(String exeName) throws IOException {
Process pgrep = new ProcessBuilder("pgrep", exeName)
.redirectErrorStream(true)
.start();
Scanner scanner = new Scanner(pgrep.getInputStream());
return Long.parseLong(scanner.nextLine());
}
@Override
public void killProcess(long pid) throws IOException {
new ProcessBuilder("kill", String.valueOf(pid))
.redirectErrorStream(true)
.start();
}
@Override
public boolean isPidStillThere(long pid) {
return Paths.get("/proc", String.valueOf(pid)).toFile().isDirectory();
}
}
/*
* Broken & incomplete implementation of java.lang.Process, implemented as an exercise.
* (Kids, don't do this at home)
*/
static class DummyProcess extends Process {
private final long pid;
private final ProcessTool tool;
DummyProcess(long pid, ProcessTool tool) {
this.pid = pid;
this.tool = tool;
}
@Override
public OutputStream getOutputStream() {
return null; // DANGER. This cannot be implemented for non-child process.
}
@Override
public InputStream getInputStream() {
return null; // DANGER. This cannot be implemented for non-child process.
}
@Override
public InputStream getErrorStream() {
return null; // DANGER. This cannot be implemented for non-child process.
}
@Override
public int waitFor() throws InterruptedException {
// Very sub-optimal implementation
boolean isPidPresent = isPidStillThere();
while (isPidPresent) {
Thread.sleep(500);
isPidPresent = isPidStillThere();
}
return 0;
}
@Override
public int exitValue() {
// For example, this is dangerous, as Process.isAlive() will call this, and determine process is not alive.
// Also, it cannot be implemented correctly, it's not possible to tell what was exit value.
// At best we could throw IllegalThreadStateException when process is still alive.
return 0;
}
@Override
public void destroy() {
try {
tool.killProcess(pid);
} catch (IOException e) {
throw new RuntimeException("Failed to kill process " + pid, e);
}
}
private boolean isPidStillThere() {
return tool.isPidStillThere(pid);
}
}
}