src/solaris/classes/java/lang/UNIXProcess.java

Print this page

        

*** 1,7 **** /* ! * Copyright (c) 1995, 2013, 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 --- 1,7 ---- /* ! * 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,45 **** import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Arrays; import java.util.concurrent.Executors; import java.util.concurrent.Executor; - import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.security.AccessController; import static java.security.AccessController.doPrivileged; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; --- 33,46 ---- 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.Set; import java.util.concurrent.Executors; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.security.AccessController; import static java.security.AccessController.doPrivileged; import java.security.PrivilegedAction; import java.security.PrivilegedActionException;
*** 47,58 **** /** * java.lang.Process subclass in the UNIX environment. * * @author Mario Wolczko and Ross Knippel. ! * @author Konstantin Kladko (ported to Linux) * @author Martin Buchholz */ final class UNIXProcess extends Process { private static final sun.misc.JavaIOFileDescriptorAccess fdAccess = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); --- 48,61 ---- /** * java.lang.Process subclass in the UNIX environment. * * @author Mario Wolczko and Ross Knippel. ! * @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 { private static final sun.misc.JavaIOFileDescriptorAccess fdAccess = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
*** 61,82 **** private boolean hasExited; private /* final */ OutputStream stdin; private /* final */ InputStream stdout; private /* final */ InputStream stderr; private static enum LaunchMechanism { ! FORK(1), ! VFORK(3); ! private int value; ! LaunchMechanism(int x) {value = x;} ! }; ! ! /* default is VFORK on Linux */ ! private static final LaunchMechanism launchMechanism; ! private static byte[] helperpath; private static byte[] toCString(String s) { if (s == null) return null; byte[] bytes = s.getBytes(); --- 64,167 ---- private boolean hasExited; private /* final */ OutputStream stdin; private /* final */ InputStream stdout; private /* final */ InputStream stderr; + private /* final */ DeferredCloseInputStream stdout_inner_stream; // only used on Solaris private static enum LaunchMechanism { ! // order IS important! ! FORK, ! POSIX_SPAWN, ! VFORK ! } ! ! private static enum Platform { ! ! LINUX(LaunchMechanism.VFORK, EnumSet.of(LaunchMechanism.FORK, LaunchMechanism.VFORK)), ! ! BSD(LaunchMechanism.POSIX_SPAWN, EnumSet.of(LaunchMechanism.FORK, LaunchMechanism.POSIX_SPAWN)), ! ! SOLARIS(LaunchMechanism.POSIX_SPAWN, EnumSet.of(LaunchMechanism.FORK, LaunchMechanism.POSIX_SPAWN)), ! ! AIX(LaunchMechanism.POSIX_SPAWN, EnumSet.of(LaunchMechanism.FORK, LaunchMechanism.POSIX_SPAWN)); ! ! final LaunchMechanism defaultLaunchMechanism; ! final Set<LaunchMechanism> validLaunchMechanisms; ! ! Platform(LaunchMechanism defaultLaunchMechanism, Set<LaunchMechanism> validLaunchMechanisms) { ! this.defaultLaunchMechanism = defaultLaunchMechanism; ! this.validLaunchMechanisms = validLaunchMechanisms; ! } ! ! private String helperPath(String javahome, String osArch) { ! switch (this) { ! case SOLARIS: ! if (osArch.equals("x86")) { osArch = "i386"; } ! else if (osArch.equals("x86_64")) { osArch = "amd64"; } ! // fall through... ! case LINUX: ! case AIX: ! return javahome + "/lib/" + osArch + "/jspawnhelper"; ! ! case BSD: ! return javahome + "/lib/jspawnhelper"; ! ! default: throw new AssertionError("Unsupported platform: " + this); ! } ! } ! ! String helperPath() { ! return AccessController.doPrivileged( ! (PrivilegedAction<String>) () -> ! helperPath(System.getProperty("java.home"), System.getProperty("os.arch")) ! ); ! } ! LaunchMechanism launchMechanism() { ! return AccessController.doPrivileged( ! (PrivilegedAction<LaunchMechanism>) () -> { ! 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 || !validLaunchMechanisms.contains(lm)) { ! throw new Error( ! s + " is not a supported " + ! "process launch mechanism on this platform." ! ); ! } ! return lm; ! } ! ); ! } ! ! static Platform get() { ! String osName = AccessController.doPrivileged( ! (PrivilegedAction<String>) () -> System.getProperty("os.name") ! ); ! ! if (osName.equals("Linux")) { return LINUX; } ! if (osName.contains("OS X")) { return BSD; } ! if (osName.equals("SunOS")) { return SOLARIS; } ! if (osName.equals("AIX")) { return AIX; } ! ! throw new Error(osName + " is not a supported OS platform."); ! } ! } ! ! private static final Platform platform = Platform.get(); ! private static final LaunchMechanism launchMechanism = platform.launchMechanism(); ! private static final byte[] helperpath = toCString(platform.helperPath()); private static byte[] toCString(String s) { if (s == null) return null; byte[] bytes = s.getBytes();
*** 86,117 **** 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); /** * Create a process. Depending on the mode flag, this is done by --- 171,180 ----
*** 139,206 **** int[] fds, boolean redirectErrorStream) throws IOException; /** ! * The thread factory used to create "process reaper" daemon threads. */ ! private static class ProcessReaperThreadFactory implements ThreadFactory { ! private final static ThreadGroup group = getRootThreadGroup(); ! ! private static ThreadGroup getRootThreadGroup() { ! return doPrivileged(new PrivilegedAction<ThreadGroup> () { ! public ThreadGroup run() { ThreadGroup root = Thread.currentThread().getThreadGroup(); while (root.getParent() != null) root = root.getParent(); return root; ! }}); ! } ! public Thread newThread(Runnable grimReaper) { // Our thread stack requirement is quite modest. ! Thread t = new Thread(group, grimReaper, "process reaper", 32768); t.setDaemon(true); // A small attempt (probably futile) to avoid priority inversion t.setPriority(Thread.MAX_PRIORITY); return t; ! } ! } ! ! /** ! * The thread pool of "process reaper" daemon threads. ! */ ! private static final Executor processReaperExecutor = ! doPrivileged(new PrivilegedAction<Executor>() { ! public Executor run() { ! return Executors.newCachedThreadPool ! (new ProcessReaperThreadFactory()); ! }}); UNIXProcess(final byte[] prog, final byte[] argBlock, final int argc, final byte[] envBlock, final int envc, final byte[] dir, final int[] fds, final boolean redirectErrorStream) throws IOException { ! pid = forkAndExec(launchMechanism.value, helperpath, prog, argBlock, argc, envBlock, envc, dir, fds, redirectErrorStream); try { ! doPrivileged(new PrivilegedExceptionAction<Void>() { ! public Void run() throws IOException { initStreams(fds); return null; ! }}); } catch (PrivilegedActionException ex) { throw (IOException) ex.getException(); } } --- 202,258 ---- int[] fds, boolean redirectErrorStream) throws IOException; /** ! * Root thread group for threads created by processReaperExecutor's ThreadFactory */ ! private static final ThreadGroup rootThreadGroup = ! doPrivileged((PrivilegedAction<ThreadGroup>) () -> { ThreadGroup root = Thread.currentThread().getThreadGroup(); while (root.getParent() != null) root = root.getParent(); return root; ! }); ! /** ! * The thread pool of "process reaper" daemon threads. ! */ ! private static final Executor processReaperExecutor = ! doPrivileged((PrivilegedAction<Executor>) () -> ! Executors.newCachedThreadPool(grimReaper -> { // Our thread stack requirement is quite modest. ! Thread t = new Thread(rootThreadGroup, grimReaper, "process reaper", 32768); t.setDaemon(true); // A small attempt (probably futile) to avoid priority inversion t.setPriority(Thread.MAX_PRIORITY); return t; ! }) ! ); UNIXProcess(final byte[] prog, final byte[] argBlock, final int argc, final byte[] envBlock, final int envc, final byte[] dir, final int[] fds, final boolean redirectErrorStream) throws IOException { ! pid = forkAndExec(launchMechanism.ordinal() + 1, helperpath, prog, argBlock, argc, envBlock, envc, dir, fds, redirectErrorStream); try { ! doPrivileged((PrivilegedExceptionAction<Void>) () -> { initStreams(fds); return null; ! }); } catch (PrivilegedActionException ex) { throw (IOException) ex.getException(); } }
*** 209,218 **** --- 261,273 ---- fdAccess.set(fileDescriptor, fd); return fileDescriptor; } void initStreams(int[] fds) throws IOException { + switch (platform) { + case LINUX: + case BSD: stdin = (fds[0] == -1) ? ProcessBuilder.NullOutputStream.INSTANCE : new ProcessPipeOutputStream(fds[0]); stdout = (fds[1] == -1) ?
*** 221,252 **** 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(); } public OutputStream getOutputStream() { return stdin; } --- 276,375 ---- stderr = (fds[2] == -1) ? ProcessBuilder.NullInputStream.INSTANCE : new ProcessPipeInputStream(fds[2]); ! processReaperExecutor.execute(() -> { int exitcode = waitForProcessExit(pid); synchronized (this) { this.exitcode = exitcode; ! this.hasExited = true; ! this.notifyAll(); } if (stdout instanceof ProcessPipeInputStream) ((ProcessPipeInputStream) stdout).processExited(); if (stderr instanceof ProcessPipeInputStream) ((ProcessPipeInputStream) stderr).processExited(); if (stdin instanceof ProcessPipeOutputStream) ((ProcessPipeOutputStream) stdin).processExited(); + }); + break; + + case SOLARIS: + 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(() -> { + int exitcode = waitForProcessExit(pid); + + synchronized (this) { + this.exitcode = exitcode; + this.hasExited = true; + this.notifyAll(); + } + }); + break; + + case AIX: + 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(() -> { + int exitcode = waitForProcessExit(pid); + + synchronized (this) { + this.exitcode = exitcode; + this.hasExited = true; + this.notifyAll(); + } + + if (stdout instanceof DeferredCloseProcessPipeInputStream) + ((DeferredCloseProcessPipeInputStream) stdout).processExited(); + + if (stderr instanceof DeferredCloseProcessPipeInputStream) + ((DeferredCloseProcessPipeInputStream) stderr).processExited(); + + if (stdin instanceof ProcessPipeOutputStream) + ((ProcessPipeOutputStream) stdin).processExited(); + }); + break; + + default: throw new AssertionError("Unsupported platform: " + platform); + } } public OutputStream getOutputStream() { return stdin; }
*** 290,300 **** --- 413,428 ---- } return exitcode; } private static native void destroyProcess(int pid, boolean force); + private void destroy(boolean force) { + switch (platform) { + case LINUX: + case BSD: + case AIX: // 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
*** 304,313 **** --- 432,468 ---- destroyProcess(pid, force); } try { stdin.close(); } catch (IOException ignored) {} try { stdout.close(); } catch (IOException ignored) {} try { stderr.close(); } catch (IOException ignored) {} + break; + + case SOLARIS: + // 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(); + if (stdout_inner_stream != null) + stdout_inner_stream.closeDeferred(stdout); + if (stderr instanceof DeferredCloseInputStream) + ((DeferredCloseInputStream) stderr) + .closeDeferred(stderr); + } catch (IOException e) { + // ignore + } + } + break; + + default: throw new AssertionError("Unsupported platform: " + platform); + } } public void destroy() { destroy(false); }
*** 336,346 **** * * 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 final Object closeLock = new Object(); ProcessPipeInputStream(int fd) { super(new FileInputStream(newFileDescriptor(fd))); } --- 491,501 ---- * * 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. */ ! private static class ProcessPipeInputStream extends BufferedInputStream { private final Object closeLock = new Object(); ProcessPipeInputStream(int fd) { super(new FileInputStream(newFileDescriptor(fd))); }
*** 386,396 **** /** * 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 { ProcessPipeOutputStream(int fd) { super(new FileOutputStream(newFileDescriptor(fd))); } /** Called by the process reaper thread when the process exits. */ --- 541,551 ---- /** * 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. */ ! 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,410 **** --- 560,816 ---- } 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; + } + } + } + } }