package net.pms.util; import net.pms.PMS; import net.pms.io.Gob; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Field; // see https://code.google.com/p/ps3mediaserver/issues/detail?id=680 // for background/issues/discussion related to this class public class ProcessUtil { private static final Logger logger = LoggerFactory.getLogger(ProcessUtil.class); // how long to wait in milliseconds until a kill -TERM on Unix has been deemed to fail private static final int TERM_TIMEOUT = 10000; // how long to wait in milliseconds until a kill -ALRM on Unix has been deemed to fail private static final int ALRM_TIMEOUT = 2000; // work around a Java bug // see: http://kylecartmell.com/?p=9 public static int waitFor(Process p) { int exit = -1; try { exit = p.waitFor(); } catch (InterruptedException e) { Thread.interrupted(); } return exit; } // get the process ID on Unix (returns null otherwise) public static Integer getProcessID(Process p) { Integer pid = null; if (p != null && p.getClass().getName().equals("java.lang.UNIXProcess")) { try { Field f = p.getClass().getDeclaredField("pid"); f.setAccessible(true); pid = f.getInt(p); } catch (Throwable e) { logger.debug("Can't determine the Unix process ID: " + e.getMessage()); } } return pid; } // kill -9 a Unix process public static void kill(Integer pid) { kill(pid, 9); } /* * FIXME: this is a hack - destroy() *should* work * * call chain (innermost last): * * WaitBufferedInputStream.close * BufferedOutputFile.detachInputStream * ProcessWrapperImpl.stopProcess * ProcessUtil.destroy * ProcessUtil.kill * * my best guess is that the process's stdout/stderr streams * aren't being/haven't been fully/promptly consumed. * From the abovelinked article: * * The Java 6 API clearly states that failure to promptly * “read the output stream of the subprocess may cause the subprocess * to block, and even deadlock. * * This is corroborated by the fact that destroy() works fine if the * process is allowed to run to completion: * * https://code.google.com/p/ps3mediaserver/issues/detail?id=680#c11 */ // send a Unix process the specified signal public static boolean kill(Integer pid, int signal) { boolean killed = false; logger.warn("Sending kill -" + signal + " to the Unix process: " + pid); try { Process process = Runtime.getRuntime().exec("kill -" + signal + " " + pid); // "Gob": a cryptic name for (e.g.) StreamGobbler - i.e. a stream // consumer that reads and discards the stream new Gob(process.getErrorStream()).start(); new Gob(process.getInputStream()).start(); int exit = waitFor(process); if (exit == 0) { killed = true; logger.debug("Successfully sent kill -" + signal + " to the Unix process: " + pid); } } catch (IOException e) { logger.error("Error calling: kill -" + signal + " " + pid, e); } return killed; } // destroy a process safely (kill -TERM on Unix) public static void destroy(final Process p) { if (p != null) { final Integer pid = getProcessID(p); if (pid != null) { // Unix only logger.trace("Killing the Unix process: " + pid); Runnable r = new Runnable() { public void run() { try { Thread.sleep(TERM_TIMEOUT); } catch (InterruptedException e) { } try { p.exitValue(); } catch (IllegalThreadStateException itse) { // still running: nuke it // kill -14 (ALRM) works (for MEncoder) and is less dangerous than kill -9 // so try that first if (!kill(pid, 14)) { try { // This is a last resort, so let's not be too eager Thread.sleep(ALRM_TIMEOUT); } catch (InterruptedException ie) { } kill(pid, 9); } } } }; Thread failsafe = new Thread(r, "Process Destroyer"); failsafe.start(); } p.destroy(); } } public static String getShortFileNameIfWideChars(String name) { return PMS.get().getRegistry().getShortPathNameW(name); } // Run cmd and return combined stdout/stderr public static String run(String... cmd) { try { ProcessBuilder pb = new ProcessBuilder(cmd); pb.redirectErrorStream(true); Process p = pb.start(); BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); String line; StringBuilder output = new StringBuilder(); while ((line = br.readLine()) != null) { output.append(line).append("\n"); } return output.toString(); } catch (Exception e) {} return ""; } }