src/solaris/classes/java/lang/UNIXProcess.java
Print this page
@@ -1,7 +1,7 @@
/*
- * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
@@ -33,10 +33,11 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
+import java.util.EnumSet;
import java.util.concurrent.Executors;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.security.AccessController;
@@ -47,36 +48,234 @@
/**
* java.lang.Process subclass in the UNIX environment.
*
* @author Mario Wolczko and Ross Knippel.
- * @author Konstantin Kladko (ported to Linux)
+ * @author Konstantin Kladko (ported to Linux and Bsd)
* @author Martin Buchholz
+ * @author Volker Simonis (ported to AIX)
+ * @author Peter Levart (merged UNIX variants into common source)
*/
-final class UNIXProcess extends Process {
+abstract class UNIXProcess extends Process {
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
- private final int pid;
- private int exitcode;
- private boolean hasExited;
-
- private /* final */ OutputStream stdin;
- private /* final */ InputStream stdout;
- private /* final */ InputStream stderr;
+ final int pid;
+ int exitcode;
+ boolean hasExited;
- private static enum LaunchMechanism {
- FORK(1),
- VFORK(3);
+ /* final */ OutputStream stdin;
+ /* final */ InputStream stdout;
+ /* final */ InputStream stderr;
+
+ private static enum Os {
+
+ LINUX {
+ @Override
+ LaunchMechanism defaultLaunchMechanism() {
+ return LaunchMechanism.VFORK;
+ }
+
+ @Override
+ String helperPath(String javahome, String osArch) {
+ return javahome + "/lib/" + osArch + "/jspawnhelper";
+ }
+
+ @Override
+ UNIXProcess newUNIXProcess(byte[] prog,
+ byte[] argBlock, final int argc,
+ byte[] envBlock, final int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream)
+ throws IOException {
+
+ return new LinuxOrBsdProcess(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ }
+ },
+
+ BSD {
+ @Override
+ LaunchMechanism defaultLaunchMechanism() {
+ return LaunchMechanism.POSIX_SPAWN;
+ }
+
+ @Override
+ String helperPath(String javahome, String osArch) {
+ return javahome + "/lib/jspawnhelper";
+ }
+
+ @Override
+ UNIXProcess newUNIXProcess(byte[] prog,
+ byte[] argBlock, final int argc,
+ byte[] envBlock, final int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream)
+ throws IOException {
+
+ return new LinuxOrBsdProcess(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ }
+ },
+
+ SOLARIS {
+ @Override
+ LaunchMechanism defaultLaunchMechanism() {
+ return LaunchMechanism.POSIX_SPAWN;
+ }
+
+ @Override
+ String helperPath(String javahome, String osArch) {
+ if (osArch.equals("x86")) {
+ osArch = "i386";
+ } else if (osArch.equals("x86_64")) {
+ osArch = "amd64";
+ }
+ return javahome + "/lib/" + osArch + "/jspawnhelper";
+ }
+
+ @Override
+ UNIXProcess newUNIXProcess(byte[] prog,
+ byte[] argBlock, final int argc,
+ byte[] envBlock, final int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream)
+ throws IOException {
+
+ return new SolarisProcess(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ }
+ },
- private int value;
- LaunchMechanism(int x) {value = x;}
+ AIX {
+ @Override
+ LaunchMechanism defaultLaunchMechanism() {
+ return LaunchMechanism.POSIX_SPAWN;
+ }
+
+ @Override
+ String helperPath(String javahome, String osArch) {
+ return javahome + "/lib/" + osArch + "/jspawnhelper";
+ }
+
+ @Override
+ UNIXProcess newUNIXProcess(byte[] prog,
+ byte[] argBlock, final int argc,
+ byte[] envBlock, final int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream)
+ throws IOException {
+
+ return new AixProcess(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ }
};
- /* default is VFORK on Linux */
- private static final LaunchMechanism launchMechanism;
- private static byte[] helperpath;
+ abstract LaunchMechanism defaultLaunchMechanism();
+
+ abstract String helperPath(String javahome, String osArch);
+
+ abstract UNIXProcess newUNIXProcess(byte[] prog,
+ byte[] argBlock, final int argc,
+ byte[] envBlock, final int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream) throws IOException;
+
+ String helperPath() {
+ return AccessController.doPrivileged(
+ new PrivilegedAction<String>() {
+ @Override
+ public String run() {
+ return helperPath(System.getProperty("java.home"), System.getProperty("os.arch"));
+ }
+ }
+ );
+ }
+
+ LaunchMechanism launchMechanism() {
+ return AccessController.doPrivileged(
+ new PrivilegedAction<LaunchMechanism>() {
+ @Override
+ public LaunchMechanism run() {
+ String s = System.getProperty("jdk.lang.Process.launchMechanism");
+ LaunchMechanism lm;
+ if (s == null) {
+ lm = defaultLaunchMechanism();
+ s = lm.name().toLowerCase();
+ } else {
+ try {
+ lm = LaunchMechanism.valueOf(s.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ lm = null;
+ }
+ }
+ if (lm == null || !lm.validOnOses.contains(Os.this)) {
+ throw new Error(
+ s + " is not a supported " +
+ "process launch mechanism on this platform."
+ );
+ }
+ return lm;
+ }
+ }
+ );
+ }
+
+ static Os get() {
+ String osName = AccessController.doPrivileged(
+ new PrivilegedAction<String>() {
+ public String run() { return System.getProperty("os.name"); }
+ }
+ );
+
+ if (osName.equals("Linux")) { return LINUX; }
+ if (osName.startsWith("Mac")) { return BSD; }
+ if (osName.equals("Solaris") || osName.equals("SunOS")) { return SOLARIS; }
+ if (osName.equals("AIX")) { return AIX; }
+
+ throw new Error(osName + " is not a supported OS platform.");
+ }
+ }
+
+ private static enum LaunchMechanism {
+ FORK(1, EnumSet.of(Os.LINUX, Os.BSD, Os.SOLARIS, Os.AIX)),
+ POSIX_SPAWN(2, EnumSet.of(Os.BSD, Os.SOLARIS, Os.AIX)),
+ VFORK(3, EnumSet.of(Os.LINUX));
+
+ final int value;
+ final EnumSet<Os> validOnOses;
+
+ LaunchMechanism(int value, EnumSet<Os> validOnOses) {
+ this.value = value;
+ this.validOnOses = validOnOses;
+ }
+ }
+
+ private static final Os OS = Os.get();
+ private static final LaunchMechanism launchMechanism = OS.launchMechanism();
+ private static final byte[] helperpath = toCString(OS.helperPath());
private static byte[] toCString(String s) {
if (s == null)
return null;
byte[] bytes = s.getBytes();
@@ -86,34 +285,12 @@
bytes.length);
result[result.length-1] = (byte)0;
return result;
}
- static {
- launchMechanism = AccessController.doPrivileged(
- new PrivilegedAction<LaunchMechanism>()
- {
- public LaunchMechanism run() {
- String javahome = System.getProperty("java.home");
- String osArch = System.getProperty("os.arch");
-
- helperpath = toCString(javahome + "/lib/" + osArch + "/jspawnhelper");
- String s = System.getProperty(
- "jdk.lang.Process.launchMechanism", "vfork");
-
- try {
- return LaunchMechanism.valueOf(s.toUpperCase());
- } catch (IllegalArgumentException e) {
- throw new Error(s + " is not a supported " +
- "process launch mechanism on this platform.");
- }
- }
- });
- }
-
/* this is for the reaping thread */
- private native int waitForProcessExit(int pid);
+ native int waitForProcessExit(int pid);
/**
* Create a process. Depending on the mode flag, this is done by
* one of the following mechanisms.
* - fork(2) and exec(2)
@@ -167,17 +344,33 @@
}
/**
* The thread pool of "process reaper" daemon threads.
*/
- private static final Executor processReaperExecutor =
+ static final Executor processReaperExecutor =
doPrivileged(new PrivilegedAction<Executor>() {
public Executor run() {
return Executors.newCachedThreadPool
(new ProcessReaperThreadFactory());
}});
+ static UNIXProcess newInstance(byte[] prog,
+ byte[] argBlock, final int argc,
+ byte[] envBlock, final int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream)
+ throws IOException {
+
+ return OS.newUNIXProcess(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ }
+
UNIXProcess(final byte[] prog,
final byte[] argBlock, final int argc,
final byte[] envBlock, final int envc,
final byte[] dir,
final int[] fds,
@@ -208,46 +401,11 @@
FileDescriptor fileDescriptor = new FileDescriptor();
fdAccess.set(fileDescriptor, fd);
return fileDescriptor;
}
- void initStreams(int[] fds) throws IOException {
- stdin = (fds[0] == -1) ?
- ProcessBuilder.NullOutputStream.INSTANCE :
- new ProcessPipeOutputStream(fds[0]);
-
- stdout = (fds[1] == -1) ?
- ProcessBuilder.NullInputStream.INSTANCE :
- new ProcessPipeInputStream(fds[1]);
-
- stderr = (fds[2] == -1) ?
- ProcessBuilder.NullInputStream.INSTANCE :
- new ProcessPipeInputStream(fds[2]);
-
- processReaperExecutor.execute(new Runnable() {
- public void run() {
- int exitcode = waitForProcessExit(pid);
- UNIXProcess.this.processExited(exitcode);
- }});
- }
-
- void processExited(int exitcode) {
- synchronized (this) {
- this.exitcode = exitcode;
- hasExited = true;
- notifyAll();
- }
-
- if (stdout instanceof ProcessPipeInputStream)
- ((ProcessPipeInputStream) stdout).processExited();
-
- if (stderr instanceof ProcessPipeInputStream)
- ((ProcessPipeInputStream) stderr).processExited();
-
- if (stdin instanceof ProcessPipeOutputStream)
- ((ProcessPipeOutputStream) stdin).processExited();
- }
+ abstract void initStreams(int[] fds) throws IOException;
public OutputStream getOutputStream() {
return stdin;
}
@@ -289,26 +447,13 @@
throw new IllegalThreadStateException("process hasn't exited");
}
return exitcode;
}
- private static native void destroyProcess(int pid, boolean force);
- private void destroy(boolean force) {
- // There is a risk that pid will be recycled, causing us to
- // kill the wrong process! So we only terminate processes
- // that appear to still be running. Even with this check,
- // there is an unavoidable race condition here, but the window
- // is very small, and OSes try hard to not recycle pids too
- // soon, so this is quite safe.
- synchronized (this) {
- if (!hasExited)
- destroyProcess(pid, force);
- }
- try { stdin.close(); } catch (IOException ignored) {}
- try { stdout.close(); } catch (IOException ignored) {}
- try { stderr.close(); } catch (IOException ignored) {}
- }
+ static native void destroyProcess(int pid, boolean force);
+
+ abstract void destroy(boolean force);
public void destroy() {
destroy(false);
}
@@ -336,11 +481,11 @@
*
* This is tricky because we do not want the user-level InputStream to be
* closed until the user invokes close(), and we need to continue to be
* able to read any buffered data lingering in the OS pipe buffer.
*/
- static class ProcessPipeInputStream extends BufferedInputStream {
+ private static class ProcessPipeInputStream extends BufferedInputStream {
private final Object closeLock = new Object();
ProcessPipeInputStream(int fd) {
super(new FileInputStream(newFileDescriptor(fd)));
}
@@ -386,11 +531,11 @@
/**
* A buffered output stream for a subprocess pipe file descriptor
* that allows the underlying file descriptor to be reclaimed when
* the process exits, via the processExited hook.
*/
- static class ProcessPipeOutputStream extends BufferedOutputStream {
+ private static class ProcessPipeOutputStream extends BufferedOutputStream {
ProcessPipeOutputStream(int fd) {
super(new FileOutputStream(newFileDescriptor(fd)));
}
/** Called by the process reaper thread when the process exits. */
@@ -405,6 +550,486 @@
}
this.out = ProcessBuilder.NullOutputStream.INSTANCE;
}
}
}
+
+ // A FileInputStream that supports the deferment of the actual close
+ // operation until the last pending I/O operation on the stream has
+ // finished. This is required on Solaris because we must close the stdin
+ // and stdout streams in the destroy method in order to reclaim the
+ // underlying file descriptors. Doing so, however, causes any thread
+ // currently blocked in a read on one of those streams to receive an
+ // IOException("Bad file number"), which is incompatible with historical
+ // behavior. By deferring the close we allow any pending reads to see -1
+ // (EOF) as they did before.
+ //
+ private static class DeferredCloseInputStream extends FileInputStream
+ {
+ DeferredCloseInputStream(FileDescriptor fd) {
+ super(fd);
+ }
+
+ private Object lock = new Object(); // For the following fields
+ private boolean closePending = false;
+ private int useCount = 0;
+ private InputStream streamToClose;
+
+ private void raise() {
+ synchronized (lock) {
+ useCount++;
+ }
+ }
+
+ private void lower() throws IOException {
+ synchronized (lock) {
+ useCount--;
+ if (useCount == 0 && closePending) {
+ streamToClose.close();
+ }
+ }
+ }
+
+ // stc is the actual stream to be closed; it might be this object, or
+ // it might be an upstream object for which this object is downstream.
+ //
+ private void closeDeferred(InputStream stc) throws IOException {
+ synchronized (lock) {
+ if (useCount == 0) {
+ stc.close();
+ } else {
+ closePending = true;
+ streamToClose = stc;
+ }
+ }
+ }
+
+ public void close() throws IOException {
+ synchronized (lock) {
+ useCount = 0;
+ closePending = false;
+ }
+ super.close();
+ }
+
+ public int read() throws IOException {
+ raise();
+ try {
+ return super.read();
+ } finally {
+ lower();
+ }
+ }
+
+ public int read(byte[] b) throws IOException {
+ raise();
+ try {
+ return super.read(b);
+ } finally {
+ lower();
+ }
+ }
+
+ public int read(byte[] b, int off, int len) throws IOException {
+ raise();
+ try {
+ return super.read(b, off, len);
+ } finally {
+ lower();
+ }
+ }
+
+ public long skip(long n) throws IOException {
+ raise();
+ try {
+ return super.skip(n);
+ } finally {
+ lower();
+ }
+ }
+
+ public int available() throws IOException {
+ raise();
+ try {
+ return super.available();
+ } finally {
+ lower();
+ }
+ }
+ }
+
+ /**
+ * A buffered input stream for a subprocess pipe file descriptor
+ * that allows the underlying file descriptor to be reclaimed when
+ * the process exits, via the processExited hook.
+ *
+ * This is tricky because we do not want the user-level InputStream to be
+ * closed until the user invokes close(), and we need to continue to be
+ * able to read any buffered data lingering in the OS pipe buffer.
+ *
+ * On AIX this is especially tricky, because the 'close()' system call
+ * will block if another thread is at the same time blocked in a file
+ * operation (e.g. 'read()') on the same file descriptor. We therefore
+ * combine 'ProcessPipeInputStream' approach used on Linux and Bsd
+ * with the DeferredCloseInputStream approach used on Solaris. This means
+ * that every potentially blocking operation on the file descriptor
+ * increments a counter before it is executed and decrements it once it
+ * finishes. The 'close()' operation will only be executed if there are
+ * no pending operations. Otherwise it is deferred after the last pending
+ * operation has finished.
+ *
+ */
+ private static class DeferredCloseProcessPipeInputStream extends BufferedInputStream {
+ private final Object closeLock = new Object();
+ private int useCount = 0;
+ private boolean closePending = false;
+
+ DeferredCloseProcessPipeInputStream(int fd) {
+ super(new FileInputStream(newFileDescriptor(fd)));
+ }
+
+ private InputStream drainInputStream(InputStream in)
+ throws IOException {
+ int n = 0;
+ int j;
+ byte[] a = null;
+ synchronized (closeLock) {
+ if (buf == null) // asynchronous close()?
+ return null; // discard
+ j = in.available();
+ }
+ while (j > 0) {
+ a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
+ synchronized (closeLock) {
+ if (buf == null) // asynchronous close()?
+ return null; // discard
+ n += in.read(a, n, j);
+ j = in.available();
+ }
+ }
+ return (a == null) ?
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n));
+ }
+
+ /** Called by the process reaper thread when the process exits. */
+ synchronized void processExited() {
+ try {
+ InputStream in = this.in;
+ if (in != null) {
+ InputStream stragglers = drainInputStream(in);
+ in.close();
+ this.in = stragglers;
+ }
+ } catch (IOException ignored) { }
+ }
+
+ private void raise() {
+ synchronized (closeLock) {
+ useCount++;
+ }
+ }
+
+ private void lower() throws IOException {
+ synchronized (closeLock) {
+ useCount--;
+ if (useCount == 0 && closePending) {
+ closePending = false;
+ super.close();
+ }
+ }
+ }
+
+ @Override
+ public int read() throws IOException {
+ raise();
+ try {
+ return super.read();
+ } finally {
+ lower();
+ }
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ raise();
+ try {
+ return super.read(b);
+ } finally {
+ lower();
+ }
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ raise();
+ try {
+ return super.read(b, off, len);
+ } finally {
+ lower();
+ }
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ raise();
+ try {
+ return super.skip(n);
+ } finally {
+ lower();
+ }
+ }
+
+ @Override
+ public int available() throws IOException {
+ raise();
+ try {
+ return super.available();
+ } finally {
+ lower();
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ // BufferedInputStream#close() is not synchronized unlike most other methods.
+ // Synchronizing helps avoid racing with drainInputStream().
+ synchronized (closeLock) {
+ if (useCount == 0) {
+ super.close();
+ }
+ else {
+ closePending = true;
+ }
+ }
+ }
+ }
+
+ // specific variants of UNIXProcess as subclasses...
+
+ private static final class LinuxOrBsdProcess extends UNIXProcess {
+
+ LinuxOrBsdProcess(
+ byte[] prog,
+ byte[] argBlock, int argc,
+ byte[] envBlock, int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream
+ )
+ throws IOException {
+
+ super(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ }
+
+ @Override
+ void initStreams(int[] fds) throws IOException {
+ stdin = (fds[0] == -1) ?
+ ProcessBuilder.NullOutputStream.INSTANCE :
+ new ProcessPipeOutputStream(fds[0]);
+
+ stdout = (fds[1] == -1) ?
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new ProcessPipeInputStream(fds[1]);
+
+ stderr = (fds[2] == -1) ?
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new ProcessPipeInputStream(fds[2]);
+
+ processReaperExecutor.execute(new Runnable() {
+ public void run() {
+ int exitcode = waitForProcessExit(pid);
+
+ synchronized (LinuxOrBsdProcess.this) {
+ LinuxOrBsdProcess.this.exitcode = exitcode;
+ LinuxOrBsdProcess.this.hasExited = true;
+ LinuxOrBsdProcess.this.notifyAll();
+ }
+
+ if (stdout instanceof ProcessPipeInputStream)
+ ((ProcessPipeInputStream) stdout).processExited();
+
+ if (stderr instanceof ProcessPipeInputStream)
+ ((ProcessPipeInputStream) stderr).processExited();
+
+ if (stdin instanceof ProcessPipeOutputStream)
+ ((ProcessPipeOutputStream) stdin).processExited();
+ }});
+ }
+
+ @Override
+ void destroy(boolean force) {
+ // There is a risk that pid will be recycled, causing us to
+ // kill the wrong process! So we only terminate processes
+ // that appear to still be running. Even with this check,
+ // there is an unavoidable race condition here, but the window
+ // is very small, and OSes try hard to not recycle pids too
+ // soon, so this is quite safe.
+ synchronized (this) {
+ if (!hasExited)
+ destroyProcess(pid, force);
+ }
+ try { stdin.close(); } catch (IOException ignored) {}
+ try { stdout.close(); } catch (IOException ignored) {}
+ try { stderr.close(); } catch (IOException ignored) {}
+ }
+ }
+
+ private static final class SolarisProcess extends UNIXProcess {
+
+ private /* final */ DeferredCloseInputStream stdout_inner_stream;
+
+ SolarisProcess(
+ byte[] prog,
+ byte[] argBlock, int argc,
+ byte[] envBlock, int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream
+ )
+ throws IOException {
+
+ super(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ }
+
+ @Override
+ void initStreams(int[] fds) throws IOException {
+ stdin = (fds[0] == -1) ?
+ ProcessBuilder.NullOutputStream.INSTANCE :
+ new BufferedOutputStream(
+ new FileOutputStream(newFileDescriptor(fds[0])));
+
+ stdout = (fds[1] == -1) ?
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new BufferedInputStream(
+ stdout_inner_stream =
+ new DeferredCloseInputStream(newFileDescriptor(fds[1])));
+
+ stderr = (fds[2] == -1) ?
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new DeferredCloseInputStream(newFileDescriptor(fds[2]));
+
+ /*
+ * For each subprocess forked a corresponding reaper task
+ * is submitted. That task is the only thread which waits
+ * for the subprocess to terminate and it doesn't hold any
+ * locks while doing so. This design allows waitFor() and
+ * exitStatus() to be safely executed in parallel (and they
+ * need no native code).
+ */
+ processReaperExecutor.execute(new Runnable() {
+ public void run() {
+ int exitcode = waitForProcessExit(pid);
+
+ synchronized (SolarisProcess.this) {
+ SolarisProcess.this.exitcode = exitcode;
+ SolarisProcess.this.hasExited = true;
+ SolarisProcess.this.notifyAll();
+ }
+ }});
+ }
+
+ @Override
+ synchronized void destroy(boolean force) {
+ // There is a risk that pid will be recycled, causing us to
+ // kill the wrong process! So we only terminate processes
+ // that appear to still be running. Even with this check,
+ // there is an unavoidable race condition here, but the window
+ // is very small, and OSes try hard to not recycle pids too
+ // soon, so this is quite safe.
+ if (!hasExited)
+ destroyProcess(pid, force);
+ try {
+ stdin.close();
+ if (stdout_inner_stream != null)
+ stdout_inner_stream.closeDeferred(stdout);
+ if (stderr instanceof DeferredCloseInputStream)
+ ((DeferredCloseInputStream) stderr)
+ .closeDeferred(stderr);
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ static final class AixProcess extends UNIXProcess {
+ AixProcess(
+ byte[] prog,
+ byte[] argBlock, int argc,
+ byte[] envBlock, int envc,
+ byte[] dir,
+ int[] fds,
+ boolean redirectErrorStream
+ )
+ throws IOException {
+
+ super(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ }
+
+ @Override
+ void initStreams(int[] fds) throws IOException {
+ stdin = (fds[0] == -1) ?
+ ProcessBuilder.NullOutputStream.INSTANCE :
+ new ProcessPipeOutputStream(fds[0]);
+
+ stdout = (fds[1] == -1) ?
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new DeferredCloseProcessPipeInputStream(fds[1]);
+
+ stderr = (fds[2] == -1) ?
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new DeferredCloseProcessPipeInputStream(fds[2]);
+
+ processReaperExecutor.execute(new Runnable() {
+ public void run() {
+ int exitcode = waitForProcessExit(pid);
+
+ synchronized (AixProcess.this) {
+ AixProcess.this.exitcode = exitcode;
+ AixProcess.this.hasExited = true;
+ AixProcess.this.notifyAll();
+ }
+
+ if (stdout instanceof DeferredCloseProcessPipeInputStream)
+ ((DeferredCloseProcessPipeInputStream) stdout).processExited();
+
+ if (stderr instanceof DeferredCloseProcessPipeInputStream)
+ ((DeferredCloseProcessPipeInputStream) stderr).processExited();
+
+ if (stdin instanceof ProcessPipeOutputStream)
+ ((ProcessPipeOutputStream) stdin).processExited();
+ }});
+ }
+
+ @Override
+ void destroy(boolean force) {
+ // There is a risk that pid will be recycled, causing us to
+ // kill the wrong process! So we only terminate processes
+ // that appear to still be running. Even with this check,
+ // there is an unavoidable race condition here, but the window
+ // is very small, and OSes try hard to not recycle pids too
+ // soon, so this is quite safe.
+ synchronized (this) {
+ if (!hasExited)
+ destroyProcess(pid, force);
+ }
+ try { stdin.close(); } catch (IOException ignored) {}
+ try { stdout.close(); } catch (IOException ignored) {}
+ try { stderr.close(); } catch (IOException ignored) {}
+ }
+ }
}