Java tutorial
// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. package org.openqa.selenium.os; import org.openqa.selenium.Platform; import com.google.common.io.Closeables; import java.io.IOException; import java.lang.reflect.Field; import java.util.logging.Level; import java.util.logging.Logger; import static org.openqa.selenium.Platform.WINDOWS; import static org.openqa.selenium.os.WindowsUtils.killPID; import static org.openqa.selenium.os.WindowsUtils.thisIsWindows; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.WinNT; public class ProcessUtils { private static Logger LOG = Logger.getLogger(ProcessUtils.class.getName()); /** * Waits the specified timeout for the process to die * * @param p The process to kill. * @param timeout How long to wait in milliseconds. * @return The exit code of the given process. */ private static int waitForProcessDeath(Process p, long timeout) { ProcessWaiter pw = new ProcessWaiter(p); Thread waiter = new Thread(pw); // Thread safety reviewed waiter.start(); try { waiter.join(timeout); } catch (InterruptedException e) { throw new RuntimeException("Bug? Main interrupted while waiting for process", e); } if (waiter.isAlive()) { waiter.interrupt(); } try { waiter.join(); } catch (InterruptedException e) { throw new RuntimeException("Bug? Main interrupted while waiting for dead process waiter", e); } InterruptedException ie = pw.getException(); if (ie != null) { throw new ProcessStillAliveException("Timeout waiting for process to die", ie); } return p.exitValue(); } /** * Forcibly kills a process, using OS tools like "kill" as a last resort * * @param process The process to kill. * @return The exit value of the process. */ public static int killProcess(Process process) { if (thisIsWindows()) { return killWinProcess(process); } else { return killUnixProcess(process); } } private static int killUnixProcess(Process process) { int exitValue; // first, wait a second to see if the process will die on it's own (we will likely have asked // the process to kill itself just before calling this method try { exitValue = waitForProcessDeath(process, 1000); closeAllStreamsAndDestroyProcess(process); if (exitValue == 0) { return exitValue; } } catch (Exception e) { // no? ok, no biggie, now let's force kill it... } process.destroy(); try { exitValue = waitForProcessDeath(process, 10000); closeAllStreamsAndDestroyProcess(process); } catch (ProcessStillAliveException ex) { if (Platform.getCurrent().is(Platform.WINDOWS)) { throw ex; } try { LOG.info("Process didn't die after 10 seconds"); kill9(process); exitValue = waitForProcessDeath(process, 10000); closeAllStreamsAndDestroyProcess(process); } catch (Exception e) { LOG.log(Level.WARNING, "Process refused to die after 10 seconds, and couldn't kill9 it", ex); throw new RuntimeException( "Process refused to die after 10 seconds, and couldn't kill9 it: " + e.getMessage(), ex); } } return exitValue; } private static int killWinProcess(Process process) { int exitValue; try { Field f = process.getClass().getDeclaredField("handle"); f.setAccessible(true); long hndl = f.getLong(process); Kernel32 kernel = Kernel32.INSTANCE; WinNT.HANDLE handle = new WinNT.HANDLE(); handle.setPointer(Pointer.createConstant(hndl)); int pid = kernel.GetProcessId(handle); killPID("" + pid); exitValue = waitForProcessDeath(process, 10000); } catch (Exception ex) { LOG.log(Level.WARNING, "Process refused to die after 10 seconds, and couldn't taskkill it", ex); throw new RuntimeException( "Process refused to die after 10 seconds, and couldn't taskkill it: " + ex.getMessage(), ex); } return exitValue; } private static class ProcessWaiter implements Runnable { private volatile InterruptedException t; private final Process p; public ProcessWaiter(Process p) { this.p = p; } public InterruptedException getException() { return t; } public void run() { try { p.waitFor(); } catch (InterruptedException e) { this.t = e; } } } public static class ProcessStillAliveException extends RuntimeException { public ProcessStillAliveException(String message, Throwable cause) { super(message, cause); } } private static void closeAllStreamsAndDestroyProcess(Process process) { try { Closeables.close(process.getInputStream(), true); Closeables.close(process.getErrorStream(), true); Closeables.close(process.getOutputStream(), true); } catch (IOException ignored) { } process.destroy(); } static int getProcessId(Process p) { if (Platform.getCurrent().is(WINDOWS)) { throw new IllegalStateException("UnixUtils may not be used on Windows"); } try { Field f = p.getClass().getDeclaredField("pid"); f.setAccessible(true); return (Integer) f.get(p); } catch (Exception e) { throw new RuntimeException("Couldn't detect pid", e); } } /** runs "kill -9" on the specified pid */ private static void kill9(Integer pid) { LOG.fine("kill -9 " + pid); CommandLine command = new CommandLine("kill", "-9", pid.toString()); command.execute(); String result = command.getStdOut(); int output = command.getExitCode(); LOG.fine(String.valueOf(output)); if (!command.isSuccessful()) { throw new RuntimeException("exec return code " + result + ": " + output); } } /** runs "kill -9" on the specified process */ private static void kill9(Process p) { kill9(getProcessId(p)); } }