package com.spotify.spawn; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.InputStream; import java.io.OutputStream; import java.util.concurrent.atomic.AtomicBoolean; import static com.spotify.spawn.Subprocesses.startDaemon; /** * A process that terminates if the parent exits. */ public class Subprocess { private static final Logger log = LoggerFactory.getLogger(Subprocess.class); private final AtomicBoolean killed = new AtomicBoolean(); private final Process process; private final String main; private final Integer parentExitCode; Subprocess(final Process process, final String main, final Integer parentExitCode) { this.process = process; this.main = main; this.parentExitCode = parentExitCode; monitor(); } // TODO (dano): communicate the child process pid to the parent via the trampoline using a pipe // public int pid() { // // } /** * Kill the process. */ public void kill() { killed.set(true); // TODO (dano): send SIGKILL after a timeout if process doesn't exit on SIGTERM. process.destroy(); } /** * Check if the process was killed by a call to {@link #kill()}. */ public boolean killed() { return killed.get(); } /** * Block until the process exits. */ public int join() throws InterruptedException { return process.waitFor(); } /** * Check if the process is still running. */ public boolean running() { try { process.exitValue(); return false; } catch (IllegalThreadStateException e) { return true; } } /** * Get stdin of the process. */ public OutputStream stdin() { return process.getOutputStream(); } /** * Get stdout of the process. */ public InputStream stdout() { return process.getInputStream(); } /** * Get stderr of the process. */ public InputStream stderr() { return process.getErrorStream(); } /** * Access the underlying {@link Process}. */ public Process process() { return process; } /** * Monitor the subprocess, log when it exits and optionally terminate the parent as well. */ private void monitor() { startDaemon(() -> { while (true) { try { final int exitCode = join(); if (killed()) { return; } if (parentExitCode != null) { log.error("{} exited: {}. Exiting.", exitCode); System.exit(parentExitCode); } else if (exitCode != 0) { log.warn("{} exited: {}", main, exitCode); } else { log.info("{} exited: 0"); } return; } catch (InterruptedException ignored) { } } }); } }