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) {}
+        }
+    }
 }