8077350: JEP 102 Process API Updates Implementation
Reviewed-by: chegar, plevart, psandoz, darcy, martin, alanb
--- a/jdk/make/mapfiles/libjava/mapfile-vers Fri May 29 10:28:28 2015 -0700
+++ b/jdk/make/mapfiles/libjava/mapfile-vers Fri May 29 14:04:12 2015 -0400
@@ -166,6 +166,16 @@
Java_java_lang_Package_getSystemPackage0;
Java_java_lang_Package_getSystemPackages0;
Java_java_lang_ProcessEnvironment_environ;
+ Java_java_lang_ProcessHandleImpl_getCurrentPid0;
+ Java_java_lang_ProcessHandleImpl_parent0;
+ Java_java_lang_ProcessHandleImpl_isAlive0;
+ Java_java_lang_ProcessHandleImpl_getProcessPids0;
+ Java_java_lang_ProcessHandleImpl_destroy0;
+ Java_java_lang_ProcessHandleImpl_waitForProcessExit0;
+ Java_java_lang_ProcessHandleImpl_00024Info_initIDs;
+ Java_java_lang_ProcessHandleImpl_00024Info_info0;
+ Java_java_lang_ProcessImpl_init;
+ Java_java_lang_ProcessImpl_forkAndExec;
Java_java_lang_reflect_Array_get;
Java_java_lang_reflect_Array_getBoolean;
Java_java_lang_reflect_Array_getByte;
@@ -214,10 +224,6 @@
Java_java_lang_Throwable_fillInStackTrace;
Java_java_lang_Throwable_getStackTraceDepth;
Java_java_lang_Throwable_getStackTraceElement;
- Java_java_lang_ProcessImpl_init;
- Java_java_lang_ProcessImpl_waitForProcessExit;
- Java_java_lang_ProcessImpl_forkAndExec;
- Java_java_lang_ProcessImpl_destroyProcess;
Java_java_nio_Bits_copyFromShortArray;
Java_java_nio_Bits_copyToShortArray;
Java_java_nio_Bits_copyFromIntArray;
@@ -277,7 +283,7 @@
Java_jdk_internal_jimage_concurrent_ConcurrentPReader_initIDs;
Java_jdk_internal_jimage_concurrent_ConcurrentPReader_pread;
-
+
# ZipFile.c needs this one
throwFileNotFoundException;
# zip_util.c needs this one
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/macosx/native/libjava/ProcessHandleImpl_macosx.c Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,401 @@
+/*
+ * Copyright (c) 2014, 2015, 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "jni.h"
+#include "jni_util.h"
+#include "java_lang_ProcessHandleImpl.h"
+#include "java_lang_ProcessHandleImpl_Info.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/sysctl.h>
+
+/**
+ * Implementations of ProcessHandleImpl functions for MAC OS X;
+ * are NOT common to all Unix variants.
+ */
+
+static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid);
+static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid);
+
+/*
+ * Common Unix function to lookup the uid and return the user name.
+ */
+extern jstring uidToUser(JNIEnv* env, uid_t uid);
+
+/* Field id for jString 'command' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_commandID;
+
+/* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_argumentsID;
+
+/* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_totalTimeID;
+
+/* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_startTimeID;
+
+/* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */
+static jfieldID ProcessHandleImpl_Info_userID;
+
+/* static value for clock ticks per second. */
+static long clock_ticks_per_second;
+
+/**************************************************************
+ * Static method to initialize field IDs and the ticks per second rate.
+ *
+ * Class: java_lang_ProcessHandleImpl_Info
+ * Method: initIDs
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
+ (JNIEnv *env, jclass clazz) {
+
+ CHECK_NULL(ProcessHandleImpl_Info_commandID =
+ (*env)->GetFieldID(env, clazz, "command", "Ljava/lang/String;"));
+ CHECK_NULL(ProcessHandleImpl_Info_argumentsID =
+ (*env)->GetFieldID(env, clazz, "arguments", "[Ljava/lang/String;"));
+ CHECK_NULL(ProcessHandleImpl_Info_totalTimeID =
+ (*env)->GetFieldID(env, clazz, "totalTime", "J"));
+ CHECK_NULL(ProcessHandleImpl_Info_startTimeID =
+ (*env)->GetFieldID(env, clazz, "startTime", "J"));
+ CHECK_NULL(ProcessHandleImpl_Info_userID =
+ (*env)->GetFieldID(env, clazz, "user", "Ljava/lang/String;"));
+ clock_ticks_per_second = sysconf(_SC_CLK_TCK);
+}
+
+/*
+ * Returns the parent pid of the requested pid.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: parent0
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
+(JNIEnv *env, jobject obj, jlong jpid) {
+ pid_t pid = (pid_t) jpid;
+ pid_t ppid = -1;
+
+ if (pid == getpid()) {
+ ppid = getppid();
+ } else {
+ const pid_t pid = (pid_t) jpid;
+ struct kinfo_proc kp;
+ size_t bufSize = sizeof kp;
+
+ // Read the process info for the specific pid
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
+ if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) < 0) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/RuntimeException", "sysctl failed");
+ return -1;
+ }
+ ppid = (bufSize > 0 && kp.kp_proc.p_pid == pid) ? kp.kp_eproc.e_ppid : -1;
+ }
+ return (jlong) ppid;
+}
+
+/*
+ * Returns the children of the requested pid and optionally each parent.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: getProcessPids0
+ * Signature: (J[J[J)I
+ *
+ * Use sysctl to accumulate any process whose parent pid is zero or matches.
+ * The resulting pids are stored into the array of longs.
+ * The number of pids is returned if they all fit.
+ * If the parentArray is non-null, store the parent pid.
+ * If the array is too short, excess pids are not stored and
+ * the desired length is returned.
+ */
+JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
+(JNIEnv *env, jclass clazz, jlong jpid,
+ jlongArray jarray, jlongArray jparentArray)
+{
+ size_t count = 0;
+ jlong* pids = NULL;
+ jlong* ppids = NULL;
+ size_t parentArraySize = 0;
+ size_t arraySize = 0;
+ size_t bufSize = 0;
+ pid_t pid = (pid_t) jpid;
+
+ arraySize = (*env)->GetArrayLength(env, jarray);
+ JNU_CHECK_EXCEPTION_RETURN(env, -1);
+ if (jparentArray != NULL) {
+ parentArraySize = (*env)->GetArrayLength(env, jparentArray);
+ JNU_CHECK_EXCEPTION_RETURN(env, -1);
+
+ if (arraySize != parentArraySize) {
+ JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
+ return 0;
+ }
+ }
+
+ // Get buffer size needed to read all processes
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
+ if (sysctl(mib, 4, NULL, &bufSize, NULL, 0) < 0) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/RuntimeException", "sysctl failed");
+ return -1;
+ }
+
+ // Allocate buffer big enough for all processes
+ void *buffer = malloc(bufSize);
+ if (buffer == NULL) {
+ JNU_ThrowOutOfMemoryError(env, "malloc failed");
+ return -1;
+ }
+
+ // Read process info for all processes
+ if (sysctl(mib, 4, buffer, &bufSize, NULL, 0) < 0) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/RuntimeException", "sysctl failed");
+ free(buffer);
+ return -1;
+ }
+
+ do { // Block to break out of on Exception
+ struct kinfo_proc *kp = (struct kinfo_proc *) buffer;
+ unsigned long nentries = bufSize / sizeof (struct kinfo_proc);
+ long i;
+
+ pids = (*env)->GetLongArrayElements(env, jarray, NULL);
+ if (pids == NULL) {
+ break;
+ }
+ if (jparentArray != NULL) {
+ ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
+ if (ppids == NULL) {
+ break;
+ }
+ }
+
+ // Process each entry in the buffer
+ for (i = nentries; --i >= 0; ++kp) {
+ if (pid == 0 || kp->kp_eproc.e_ppid == pid) {
+ if (count < arraySize) {
+ // Only store if it fits
+ pids[count] = (jlong) kp->kp_proc.p_pid;
+ if (ppids != NULL) {
+ // Store the parentPid
+ ppids[count] = (jlong) kp->kp_eproc.e_ppid;
+ }
+ }
+ count++; // Count to tabulate size needed
+ }
+ }
+ } while (0);
+
+ if (pids != NULL) {
+ (*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
+ }
+ if (ppids != NULL) {
+ (*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
+ }
+
+ free(buffer);
+ // If more pids than array had size for; count will be greater than array size
+ return count;
+}
+
+/**************************************************************
+ * Implementation of ProcessHandleImpl_Info native methods.
+ */
+
+/*
+ * Fill in the Info object from the OS information about the process.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: info0
+ * Signature: (J)I
+ */
+JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
+ (JNIEnv *env, jobject jinfo, jlong jpid) {
+ pid_t pid = (pid_t) jpid;
+ getStatInfo(env, jinfo, pid);
+ getCmdlineInfo(env, jinfo, pid);
+}
+
+/**
+ * Read /proc/<pid>/stat and fill in the fields of the Info object.
+ * The executable name, plus the user, system, and start times are gathered.
+ */
+static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t jpid) {
+ jlong totalTime; // nanoseconds
+ unsigned long long startTime; // microseconds
+
+ const pid_t pid = (pid_t) jpid;
+ struct kinfo_proc kp;
+ size_t bufSize = sizeof kp;
+
+ // Read the process info for the specific pid
+ int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
+
+ if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) < 0) {
+ if (errno == EINVAL) {
+ return;
+ } else {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/RuntimeException", "sysctl failed");
+ }
+ return;
+ }
+
+ // Convert the UID to the username
+ jstring name = NULL;
+ CHECK_NULL((name = uidToUser(env, kp.kp_eproc.e_ucred.cr_uid)));
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, name);
+ JNU_CHECK_EXCEPTION(env);
+
+ startTime = kp.kp_proc.p_starttime.tv_sec * 1000 +
+ kp.kp_proc.p_starttime.tv_usec / 1000;
+
+ (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
+ JNU_CHECK_EXCEPTION(env);
+
+ // Get cputime if for current process
+ if (pid == getpid()) {
+ struct rusage usage;
+ if (getrusage(RUSAGE_SELF, &usage) != 0) {
+ return;
+ }
+ jlong microsecs =
+ usage.ru_utime.tv_sec * 1000 * 1000 + usage.ru_utime.tv_usec +
+ usage.ru_stime.tv_sec * 1000 * 1000 + usage.ru_stime.tv_usec;
+ totalTime = microsecs * 1000;
+ (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, totalTime);
+ JNU_CHECK_EXCEPTION(env);
+ }
+}
+
+/**
+ * Construct the argument array by parsing the arguments from the sequence of arguments.
+ */
+static int fillArgArray(JNIEnv *env, jobject jinfo, int nargs,
+ const char *cp, const char *argsEnd) {
+ jstring str = NULL;
+ jobject argsArray;
+ int i;
+
+ if (nargs < 1) {
+ return 0;
+ }
+ // Create a String array for nargs-1 elements
+ CHECK_NULL_RETURN((argsArray = (*env)->NewObjectArray(env,
+ nargs - 1, JNU_ClassString(env), NULL)), -1);
+
+ for (i = 0; i < nargs - 1; i++) {
+ // skip to the next argument; omits arg[0]
+ cp += strnlen(cp, (argsEnd - cp)) + 1;
+
+ if (cp > argsEnd || *cp == '\0') {
+ return -2; // Off the end pointer or an empty argument is an error
+ }
+
+ CHECK_NULL_RETURN((str = JNU_NewStringPlatform(env, cp)), -1);
+
+ (*env)->SetObjectArrayElement(env, argsArray, i, str);
+ JNU_CHECK_EXCEPTION_RETURN(env, -3);
+ }
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_argumentsID, argsArray);
+ JNU_CHECK_EXCEPTION_RETURN(env, -4);
+ return 0;
+}
+
+/**
+ * Retrieve the command and arguments for the process and store them
+ * into the Info object.
+ */
+static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
+ int mib[3], maxargs, nargs, i;
+ size_t size;
+ char *args, *cp, *sp, *np;
+
+ // Get the maximum size of the arguments
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_ARGMAX;
+ size = sizeof(maxargs);
+ if (sysctl(mib, 2, &maxargs, &size, NULL, 0) == -1) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/RuntimeException", "sysctl failed");
+ return;
+ }
+
+ // Allocate an args buffer and get the arguments
+ args = (char *)malloc(maxargs);
+ if (args == NULL) {
+ JNU_ThrowOutOfMemoryError(env, "malloc failed");
+ return;
+ }
+
+ do { // a block to break out of on error
+ char *argsEnd;
+ jstring str = NULL;
+
+ mib[0] = CTL_KERN;
+ mib[1] = KERN_PROCARGS2;
+ mib[2] = pid;
+ size = (size_t) maxargs;
+ if (sysctl(mib, 3, args, &size, NULL, 0) == -1) {
+ if (errno != EINVAL) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/RuntimeException", "sysctl failed");
+ }
+ break;
+ }
+ memcpy(&nargs, args, sizeof(nargs));
+
+ cp = &args[sizeof(nargs)]; // Strings start after nargs
+ argsEnd = &args[size];
+
+ // Store the command executable path
+ if ((str = JNU_NewStringPlatform(env, cp)) == NULL) {
+ break;
+ }
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, str);
+ if ((*env)->ExceptionCheck(env)) {
+ break;
+ }
+
+ // Skip trailing nulls after the executable path
+ for (cp = cp + strnlen(cp, argsEnd - cp); cp < argsEnd; cp++) {
+ if (*cp != '\0') {
+ break;
+ }
+ }
+
+ fillArgArray(env, jinfo, nargs, cp, argsEnd);
+ } while (0);
+ // Free the arg buffer
+ free(args);
+}
+
--- a/jdk/src/java.base/share/classes/java/lang/Process.java Fri May 29 10:28:28 2015 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/Process.java Fri May 29 14:04:12 2015 -0400
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2015, 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
@@ -26,25 +26,31 @@
package java.lang;
import java.io.*;
+import java.lang.ProcessBuilder.Redirect;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
/**
+ * {@code Process} provides control of native processes started by
+ * ProcessBuilder.start and Runtime.exec.
+ * The class provides methods for performing input from the process, performing
+ * output to the process, waiting for the process to complete,
+ * checking the exit status of the process, and destroying (killing)
+ * the process.
* The {@link ProcessBuilder#start()} and
* {@link Runtime#exec(String[],String[],File) Runtime.exec}
* methods create a native process and return an instance of a
* subclass of {@code Process} that can be used to control the process
- * and obtain information about it. The class {@code Process}
- * provides methods for performing input from the process, performing
- * output to the process, waiting for the process to complete,
- * checking the exit status of the process, and destroying (killing)
- * the process.
+ * and obtain information about it.
*
* <p>The methods that create processes may not work well for special
* processes on certain native platforms, such as native windowing
* processes, daemon processes, Win16/DOS processes on Microsoft
* Windows, or shell scripts.
*
- * <p>By default, the created subprocess does not have its own terminal
+ * <p>By default, the created process does not have its own terminal
* or console. All its standard I/O (i.e. stdin, stdout, stderr)
* operations will be redirected to the parent process, where they can
* be accessed via the streams obtained using the methods
@@ -52,35 +58,49 @@
* {@link #getInputStream()}, and
* {@link #getErrorStream()}.
* The parent process uses these streams to feed input to and get output
- * from the subprocess. Because some native platforms only provide
+ * from the process. Because some native platforms only provide
* limited buffer size for standard input and output streams, failure
* to promptly write the input stream or read the output stream of
- * the subprocess may cause the subprocess to block, or even deadlock.
+ * the process may cause the process to block, or even deadlock.
*
* <p>Where desired, <a href="ProcessBuilder.html#redirect-input">
- * subprocess I/O can also be redirected</a>
+ * process I/O can also be redirected</a>
* using methods of the {@link ProcessBuilder} class.
*
- * <p>The subprocess is not killed when there are no more references to
- * the {@code Process} object, but rather the subprocess
+ * <p>The process is not killed when there are no more references to
+ * the {@code Process} object, but rather the process
* continues executing asynchronously.
*
- * <p>There is no requirement that a process represented by a {@code
+ * <p>There is no requirement that the process represented by a {@code
* Process} object execute asynchronously or concurrently with respect
* to the Java process that owns the {@code Process} object.
*
* <p>As of 1.5, {@link ProcessBuilder#start()} is the preferred way
* to create a {@code Process}.
*
+ * <p>Subclasses of Process should override the {@link #onExit()} and
+ * {@link #toHandle()} methods to provide a fully functional Process including the
+ * {@link #getPid() process id},
+ * {@link #info() information about the process},
+ * {@link #children() direct children}, and
+ * {@link #allChildren() direct and indirect children} of the process.
+ * Delegating to the underlying Process or ProcessHandle is typically
+ * easiest and most efficient.
+ *
* @since 1.0
*/
public abstract class Process {
/**
+ * Default constructor for Process.
+ */
+ public Process() {}
+
+ /**
* Returns the output stream connected to the normal input of the
- * subprocess. Output to the stream is piped into the standard
+ * process. Output to the stream is piped into the standard
* input of the process represented by this {@code Process} object.
*
- * <p>If the standard input of the subprocess has been redirected using
+ * <p>If the standard input of the process has been redirected using
* {@link ProcessBuilder#redirectInput(Redirect)
* ProcessBuilder.redirectInput}
* then this method will return a
@@ -90,42 +110,42 @@
* output stream to be buffered.
*
* @return the output stream connected to the normal input of the
- * subprocess
+ * process
*/
public abstract OutputStream getOutputStream();
/**
* Returns the input stream connected to the normal output of the
- * subprocess. The stream obtains data piped from the standard
+ * process. The stream obtains data piped from the standard
* output of the process represented by this {@code Process} object.
*
- * <p>If the standard output of the subprocess has been redirected using
+ * <p>If the standard output of the process has been redirected using
* {@link ProcessBuilder#redirectOutput(Redirect)
* ProcessBuilder.redirectOutput}
* then this method will return a
* <a href="ProcessBuilder.html#redirect-output">null input stream</a>.
*
- * <p>Otherwise, if the standard error of the subprocess has been
+ * <p>Otherwise, if the standard error of the process has been
* redirected using
* {@link ProcessBuilder#redirectErrorStream(boolean)
* ProcessBuilder.redirectErrorStream}
* then the input stream returned by this method will receive the
- * merged standard output and the standard error of the subprocess.
+ * merged standard output and the standard error of the process.
*
* <p>Implementation note: It is a good idea for the returned
* input stream to be buffered.
*
* @return the input stream connected to the normal output of the
- * subprocess
+ * process
*/
public abstract InputStream getInputStream();
/**
* Returns the input stream connected to the error output of the
- * subprocess. The stream obtains data piped from the error output
+ * process. The stream obtains data piped from the error output
* of the process represented by this {@code Process} object.
*
- * <p>If the standard error of the subprocess has been redirected using
+ * <p>If the standard error of the process has been redirected using
* {@link ProcessBuilder#redirectError(Redirect)
* ProcessBuilder.redirectError} or
* {@link ProcessBuilder#redirectErrorStream(boolean)
@@ -137,19 +157,19 @@
* input stream to be buffered.
*
* @return the input stream connected to the error output of
- * the subprocess
+ * the process
*/
public abstract InputStream getErrorStream();
/**
* Causes the current thread to wait, if necessary, until the
* process represented by this {@code Process} object has
- * terminated. This method returns immediately if the subprocess
- * has already terminated. If the subprocess has not yet
+ * terminated. This method returns immediately if the process
+ * has already terminated. If the process has not yet
* terminated, the calling thread will be blocked until the
- * subprocess exits.
+ * process exits.
*
- * @return the exit value of the subprocess represented by this
+ * @return the exit value of the process represented by this
* {@code Process} object. By convention, the value
* {@code 0} indicates normal termination.
* @throws InterruptedException if the current thread is
@@ -161,10 +181,10 @@
/**
* Causes the current thread to wait, if necessary, until the
- * subprocess represented by this {@code Process} object has
+ * process represented by this {@code Process} object has
* terminated, or the specified waiting time elapses.
*
- * <p>If the subprocess has already terminated then this method returns
+ * <p>If the process has already terminated then this method returns
* immediately with the value {@code true}. If the process has not
* terminated and the timeout value is less than, or equal to, zero, then
* this method returns immediately with the value {@code false}.
@@ -176,8 +196,8 @@
*
* @param timeout the maximum time to wait
* @param unit the time unit of the {@code timeout} argument
- * @return {@code true} if the subprocess has exited and {@code false} if
- * the waiting time elapsed before the subprocess has exited.
+ * @return {@code true} if the process has exited and {@code false} if
+ * the waiting time elapsed before the process has exited.
* @throws InterruptedException if the current thread is interrupted
* while waiting.
* @throws NullPointerException if unit is null
@@ -204,41 +224,60 @@
}
/**
- * Returns the exit value for the subprocess.
+ * Returns the exit value for the process.
*
- * @return the exit value of the subprocess represented by this
+ * @return the exit value of the process represented by this
* {@code Process} object. By convention, the value
* {@code 0} indicates normal termination.
- * @throws IllegalThreadStateException if the subprocess represented
+ * @throws IllegalThreadStateException if the process represented
* by this {@code Process} object has not yet terminated
*/
public abstract int exitValue();
/**
- * Kills the subprocess. Whether the subprocess represented by this
- * {@code Process} object is forcibly terminated or not is
+ * Kills the process.
+ * Whether the process represented by this {@code Process} object is
+ * {@link #supportsNormalTermination normally terminated} or not is
* implementation dependent.
+ * Forcible process destruction is defined as the immediate termination of a
+ * process, whereas normal termination allows the process to shut down cleanly.
+ * If the process is not alive, no action is taken.
+ * <p>
+ * The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is
+ * {@link java.util.concurrent.CompletableFuture#complete completed}
+ * when the process has terminated.
*/
public abstract void destroy();
/**
- * Kills the subprocess. The subprocess represented by this
+ * Kills the process forcibly. The process represented by this
* {@code Process} object is forcibly terminated.
+ * Forcible process destruction is defined as the immediate termination of a
+ * process, whereas normal termination allows the process to shut down cleanly.
+ * If the process is not alive, no action is taken.
+ * <p>
+ * The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is
+ * {@link java.util.concurrent.CompletableFuture#complete completed}
+ * when the process has terminated.
+ * <p>
+ * Invoking this method on {@code Process} objects returned by
+ * {@link ProcessBuilder#start} and {@link Runtime#exec} forcibly terminate
+ * the process.
*
- * <p>The default implementation of this method invokes {@link #destroy}
- * and so may not forcibly terminate the process. Concrete implementations
- * of this class are strongly encouraged to override this method with a
- * compliant implementation. Invoking this method on {@code Process}
- * objects returned by {@link ProcessBuilder#start} and
- * {@link Runtime#exec} will forcibly terminate the process.
- *
- * <p>Note: The subprocess may not terminate immediately.
+ * @implSpec
+ * The default implementation of this method invokes {@link #destroy}
+ * and so may not forcibly terminate the process.
+ * @implNote
+ * Concrete implementations of this class are strongly encouraged to override
+ * this method with a compliant implementation.
+ * @apiNote
+ * The process may not terminate immediately.
* i.e. {@code isAlive()} may return true for a brief period
* after {@code destroyForcibly()} is called. This method
* may be chained to {@code waitFor()} if needed.
*
* @return the {@code Process} object representing the
- * subprocess to be forcibly destroyed.
+ * process forcibly destroyed
* @since 1.8
*/
public Process destroyForcibly() {
@@ -247,10 +286,36 @@
}
/**
- * Tests whether the subprocess represented by this {@code Process} is
+ * Returns {@code true} if the implementation of {@link #destroy} is to
+ * normally terminate the process,
+ * Returns {@code false} if the implementation of {@code destroy}
+ * forcibly and immediately terminates the process.
+ * <p>
+ * Invoking this method on {@code Process} objects returned by
+ * {@link ProcessBuilder#start} and {@link Runtime#exec} return
+ * {@code true} or {@code false} depending on the platform implementation.
+ *
+ * @implSpec
+ * This implementation throws an instance of
+ * {@link java.lang.UnsupportedOperationException} and performs no other action.
+ *
+ * @return {@code true} if the implementation of {@link #destroy} is to
+ * normally terminate the process;
+ * otherwise, {@link #destroy} forcibly terminates the process
+ * @throws UnsupportedOperationException if the Process implementation
+ * does not support this operation
+ * @since 1.9
+ */
+ public boolean supportsNormalTermination() {
+ throw new UnsupportedOperationException(this.getClass()
+ + ".supportsNormalTermination() not supported" );
+ }
+
+ /**
+ * Tests whether the process represented by this {@code Process} is
* alive.
*
- * @return {@code true} if the subprocess represented by this
+ * @return {@code true} if the process represented by this
* {@code Process} object has not yet terminated.
* @since 1.8
*/
@@ -264,16 +329,222 @@
}
/**
- * Returns the native process id of the subprocess.
- * The native process id is an identification number that the operating
+ * Returns the native process ID of the process.
+ * The native process ID is an identification number that the operating
* system assigns to the process.
*
- * @return the native process id of the subprocess
+ * @implSpec
+ * The implementation of this method returns the process id as:
+ * {@link #toHandle toHandle().getPid()}.
+ *
+ * @return the native process id of the process
* @throws UnsupportedOperationException if the Process implementation
- * does not support this operation
+ * does not support this operation
* @since 1.9
*/
public long getPid() {
- throw new UnsupportedOperationException();
+ return toHandle().getPid();
+ }
+
+ /**
+ * Returns a {@code CompletableFuture<Process>} for the termination of the Process.
+ * The {@link java.util.concurrent.CompletableFuture} provides the ability
+ * to trigger dependent functions or actions that may be run synchronously
+ * or asynchronously upon process termination.
+ * When the process terminates the CompletableFuture is
+ * {@link java.util.concurrent.CompletableFuture#complete completed} regardless
+ * of the exit status of the process.
+ * <p>
+ * Calling {@code onExit().get()} waits for the process to terminate and returns
+ * the Process. The future can be used to check if the process is
+ * {@link java.util.concurrent.CompletableFuture#isDone done} or to
+ * {@link java.util.concurrent.CompletableFuture#get() wait} for it to terminate.
+ * {@link java.util.concurrent.CompletableFuture#cancel(boolean) Cancelling}
+ * the CompletableFuture does not affect the Process.
+ * <p>
+ * If the process is {@link #isAlive not alive} the {@link CompletableFuture}
+ * returned has been {@link java.util.concurrent.CompletableFuture#complete completed}.
+ * <p>
+ * Processes returned from {@link ProcessBuilder#start} override the
+ * default implementation to provide an efficient mechanism to wait
+ * for process exit.
+ * <p>
+ * @apiNote
+ * Using {@link #onExit() onExit} is an alternative to
+ * {@link #waitFor() waitFor} that enables both additional concurrency
+ * and convenient access to the result of the Process.
+ * Lambda expressions can be used to evaluate the result of the Process
+ * execution.
+ * If there is other processing to be done before the value is used
+ * then {@linkplain #onExit onExit} is a convenient mechanism to
+ * free the current thread and block only if and when the value is needed.
+ * <br>
+ * For example, launching a process to compare two files and get a boolean if they are identical:
+ * <pre> {@code Process p = new ProcessBuilder("cmp", "f1", "f2").start();
+ * Future<Boolean> identical = p.onExit().thenApply(p1 -> p1.exitValue() == 0);
+ * ...
+ * if (identical.get()) { ... }
+ * }</pre>
+ *
+ * @implSpec
+ * This implementation executes {@link #waitFor()} in a separate thread
+ * repeatedly until it returns successfully. If the execution of
+ * {@code waitFor} is interrupted, the thread's interrupt status is preserved.
+ * <p>
+ * When {@link #waitFor()} returns successfully the CompletableFuture is
+ * {@link java.util.concurrent.CompletableFuture#complete completed} regardless
+ * of the exit status of the process.
+ *
+ * This implementation may consume a lot of memory for thread stacks if a
+ * large number of processes are waited for concurrently.
+ * <p>
+ * External implementations should override this method and provide
+ * a more efficient implementation. For example, to delegate to the underlying
+ * process, it can do the following:
+ * <pre>{@code
+ * public CompletableFuture<Process> onExit() {
+ * return delegate.onExit().thenApply(p -> this);
+ * }
+ * }</pre>
+ *
+ * @return a new {@code CompletableFuture<Process>} for the Process
+ *
+ * @since 1.9
+ */
+ public CompletableFuture<Process> onExit() {
+ return CompletableFuture.supplyAsync(this::waitForInternal);
}
+
+ /**
+ * Wait for the process to exit by calling {@code waitFor}.
+ * If the thread is interrupted, remember the interrupted state to
+ * be restored before returning. Use ForkJoinPool.ManagedBlocker
+ * so that the number of workers in case ForkJoinPool is used is
+ * compensated when the thread blocks in waitFor().
+ *
+ * @return the Process
+ */
+ private Process waitForInternal() {
+ boolean interrupted = false;
+ while (true) {
+ try {
+ ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker() {
+ @Override
+ public boolean block() throws InterruptedException {
+ waitFor();
+ return true;
+ }
+
+ @Override
+ public boolean isReleasable() {
+ return !isAlive();
+ }
+ });
+ break;
+ } catch (InterruptedException x) {
+ interrupted = true;
+ }
+ }
+ if (interrupted) {
+ Thread.currentThread().interrupt();
+ }
+ return this;
+ }
+
+ /**
+ * Returns a ProcessHandle for the Process.
+ *
+ * {@code Process} objects returned by {@link ProcessBuilder#start} and
+ * {@link Runtime#exec} implement {@code toHandle} as the equivalent of
+ * {@link ProcessHandle#of(long) ProcessHandle.of(pid)} including the
+ * check for a SecurityManager and {@code RuntimePermission("manageProcess")}.
+ *
+ * @implSpec
+ * This implementation throws an instance of
+ * {@link java.lang.UnsupportedOperationException} and performs no other action.
+ * Subclasses should override this method to provide a ProcessHandle for the
+ * process. The methods {@link #getPid}, {@link #info}, {@link #children},
+ * and {@link #allChildren}, unless overridden, operate on the ProcessHandle.
+ *
+ * @return Returns a ProcessHandle for the Process
+ * @throws UnsupportedOperationException if the Process implementation
+ * does not support this operation
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ * @since 1.9
+ */
+ public ProcessHandle toHandle() {
+ throw new UnsupportedOperationException(this.getClass()
+ + ".toHandle() not supported");
+ }
+
+ /**
+ * Returns a snapshot of information about the process.
+ *
+ * <p> An {@link ProcessHandle.Info} instance has various accessor methods
+ * that return information about the process, if the process is alive and
+ * the information is available, otherwise {@code null} is returned.
+ *
+ * @implSpec
+ * This implementation returns information about the process as:
+ * {@link #toHandle toHandle().info()}.
+ *
+ * @return a snapshot of information about the process, always non-null
+ * @throws UnsupportedOperationException if the Process implementation
+ * does not support this operation
+ * @since 1.9
+ */
+ public ProcessHandle.Info info() {
+ return toHandle().info();
+ }
+
+ /**
+ * Returns a snapshot of the direct children of the process.
+ * A process that is {@link #isAlive not alive} has zero children.
+ * <p>
+ * <em>Note that processes are created and terminate asynchronously.
+ * There is no guarantee that a process is {@link #isAlive alive}.
+ * </em>
+ *
+ * @implSpec
+ * This implementation returns the direct children as:
+ * {@link #toHandle toHandle().children()}.
+ *
+ * @return a Stream of ProcessHandles for processes that are direct children
+ * of the process
+ * @throws UnsupportedOperationException if the Process implementation
+ * does not support this operation
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ * @since 1.9
+ */
+ public Stream<ProcessHandle> children() {
+ return toHandle().children();
+ }
+
+ /**
+ * Returns a snapshot of the direct and indirect children of the process.
+ * A process that is {@link #isAlive not alive} has zero children.
+ * <p>
+ * <em>Note that processes are created and terminate asynchronously.
+ * There is no guarantee that a process is {@link #isAlive alive}.
+ * </em>
+ *
+ * @implSpec
+ * This implementation returns all children as:
+ * {@link #toHandle toHandle().allChildren()}.
+ *
+ * @return a Stream of ProcessHandles for processes that are direct and
+ * indirect children of the process
+ * @throws UnsupportedOperationException if the Process implementation
+ * does not support this operation
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ * @since 1.9
+ */
+ public Stream<ProcessHandle> allChildren() {
+ return toHandle().allChildren();
+ }
+
+
}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/ProcessHandle.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,361 @@
+/*
+ * Copyright (c) 2014, 2015, 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Stream;
+
+/**
+ * ProcessHandle identifies and provides control of native processes. Each
+ * individual process can be monitored for liveness, list its children,
+ * get information about the process or destroy it.
+ * By comparison, {@link java.lang.Process Process} instances were started
+ * by the current process and additionally provide access to the process
+ * input, output, and error streams.
+ * <p>
+ * The native process ID is an identification number that the
+ * operating system assigns to the process.
+ * The range for process id values is dependent on the operating system.
+ * For example, an embedded system might use a 16-bit value.
+ * Status information about a process is retrieved from the native system
+ * and may change asynchronously; processes may be created or terminate
+ * spontaneously.
+ * The time between when a process terminates and the process id
+ * is reused for a new process is unpredictable.
+ * Race conditions can exist between checking the status of a process and
+ * acting upon it. When using ProcessHandles avoid assumptions
+ * about the liveness or identity of the underlying process.
+ * <p>
+ * Each ProcessHandle identifies and allows control of a process in the native
+ * system. ProcessHandles are returned from the factory methods {@link #current()},
+ * {@link #of(long)},
+ * {@link #children}, {@link #allChildren}, {@link #parent()} and
+ * {@link #allProcesses()}.
+ * <p>
+ * The {@link Process} instances created by {@link ProcessBuilder} can be queried
+ * for a ProcessHandle that provides information about the Process.
+ * ProcessHandle references should not be freely distributed.
+ *
+ * <p>
+ * A {@link java.util.concurrent.CompletableFuture} available from {@link #onExit}
+ * can be used to wait for process termination, and possibly trigger dependent
+ * actions.
+ * <p>
+ * The factory methods limit access to ProcessHandles using the
+ * SecurityManager checking the {@link RuntimePermission RuntimePermission("manageProcess")}.
+ * The ability to control processes is also restricted by the native system,
+ * ProcessHandle provides no more access to, or control over, the native process
+ * than would be allowed by a native application.
+ * <p>
+ * @implSpec
+ * In the case where ProcessHandles cannot be supported then the factory
+ * methods must consistently throw {@link java.lang.UnsupportedOperationException}.
+ * The methods of this class throw {@link java.lang.UnsupportedOperationException}
+ * if the operating system does not allow access to query or kill a process.
+ *
+ * @see Process
+ * @since 1.9
+ */
+public interface ProcessHandle extends Comparable<ProcessHandle> {
+
+ /**
+ * Returns the native process ID of the process. The native process ID is an
+ * identification number that the operating system assigns to the process.
+ *
+ * @return the native process ID of the process
+ * @throws UnsupportedOperationException if the implementation
+ * does not support this operation
+ */
+ long getPid();
+
+ /**
+ * Returns an {@code Optional<ProcessHandle>} for an existing native process.
+ *
+ * @param pid a native process ID
+ * @return an {@code Optional<ProcessHandle>} of the PID for the process;
+ * the {@code Optional} is empty if the process does not exist
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ * @throws UnsupportedOperationException if the implementation
+ * does not support this operation
+ */
+ public static Optional<ProcessHandle> of(long pid) {
+ return ProcessHandleImpl.get(pid);
+ }
+
+ /**
+ * Returns a ProcessHandle for the current process. The ProcessHandle cannot be
+ * used to destroy the current process, use {@link System#exit System.exit} instead.
+ *
+ * @return a ProcessHandle for the current process
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ * @throws UnsupportedOperationException if the implementation
+ * does not support this operation
+ */
+ public static ProcessHandle current() {
+ return ProcessHandleImpl.current();
+ }
+
+ /**
+ * Returns an {@code Optional<ProcessHandle>} for the parent process.
+ * Note that Processes in a zombie state usually don't have a parent.
+ *
+ * @return an {@code Optional<ProcessHandle>} of the parent process;
+ * the {@code Optional} is empty if the child process does not have a parent
+ * or if the parent is not available, possibly due to operating system limitations
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ */
+ Optional<ProcessHandle> parent();
+
+ /**
+ * Returns a snapshot of the current direct children of the process.
+ * A process that is {@link #isAlive not alive} has zero children.
+ * <p>
+ * <em>Note that processes are created and terminate asynchronously.
+ * There is no guarantee that a process is {@link #isAlive alive}.
+ * </em>
+ *
+ * @return a Stream of ProcessHandles for processes that are direct children
+ * of the process
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ */
+ Stream<ProcessHandle> children();
+
+ /**
+ * Returns a snapshot of the current direct and indirect children of the process.
+ * A process that is {@link #isAlive not alive} has zero children.
+ * <p>
+ * <em>Note that processes are created and terminate asynchronously.
+ * There is no guarantee that a process is {@link #isAlive alive}.
+ * </em>
+ *
+ * @return a Stream of ProcessHandles for processes that are direct and
+ * indirect children of the process
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ */
+ Stream<ProcessHandle> allChildren();
+
+ /**
+ * Returns a snapshot of all processes visible to the current process.
+ * <p>
+ * <em>Note that processes are created and terminate asynchronously. There
+ * is no guarantee that a process in the stream is alive or that no other
+ * processes may have been created since the inception of the snapshot.
+ * </em>
+ *
+ * @return a Stream of ProcessHandles for all processes
+ * @throws SecurityException if a security manager has been installed and
+ * it denies RuntimePermission("manageProcess")
+ * @throws UnsupportedOperationException if the implementation
+ * does not support this operation
+ */
+ static Stream<ProcessHandle> allProcesses() {
+ return ProcessHandleImpl.children(0);
+ }
+
+ /**
+ * Returns a snapshot of information about the process.
+ *
+ * <p> An {@code Info} instance has various accessor methods that return
+ * information about the process, if the process is alive and the
+ * information is available.
+ *
+ * @return a snapshot of information about the process, always non-null
+ */
+ Info info();
+
+ /**
+ * Information snapshot about the process.
+ * The attributes of a process vary by operating system and are not available
+ * in all implementations. Information about processes is limited
+ * by the operating system privileges of the process making the request.
+ * The return types are {@code Optional<T>} allowing explicit tests
+ * and actions if the value is available.
+ * @since 1.9
+ */
+ public interface Info {
+ /**
+ * Returns the executable pathname of the process.
+ *
+ * @return an {@code Optional<String>} of the executable pathname
+ * of the process
+ */
+ public Optional<String> command();
+
+ /**
+ * Returns an array of Strings of the arguments of the process.
+ *
+ * @return an {@code Optional<String[]>} of the arguments of the process
+ */
+ public Optional<String[]> arguments();
+
+ /**
+ * Returns the start time of the process.
+ *
+ * @return an {@code Optional<Instant>} of the start time of the process
+ */
+ public Optional<Instant> startInstant();
+
+ /**
+ * Returns the total cputime accumulated of the process.
+ *
+ * @return an {@code Optional<Duration>} for the accumulated total cputime
+ */
+ public Optional<Duration> totalCpuDuration();
+
+ /**
+ * Return the user of the process.
+ *
+ * @return an {@code Optional<String>} for the user of the process
+ */
+ public Optional<String> user();
+ }
+
+ /**
+ * Returns a {@code CompletableFuture<ProcessHandle>} for the termination
+ * of the process.
+ * The {@link java.util.concurrent.CompletableFuture} provides the ability
+ * to trigger dependent functions or actions that may be run synchronously
+ * or asynchronously upon process termination.
+ * When the process terminates the CompletableFuture is
+ * {@link java.util.concurrent.CompletableFuture#complete completed} regardless
+ * of the exit status of the process.
+ * The {@code onExit} method can be called multiple times to invoke
+ * independent actions when the process exits.
+ * <p>
+ * Calling {@code onExit().get()} waits for the process to terminate and returns
+ * the ProcessHandle. The future can be used to check if the process is
+ * {@link java.util.concurrent.CompletableFuture#isDone done} or to
+ * {@link java.util.concurrent.Future#get() wait} for it to terminate.
+ * {@link java.util.concurrent.Future#cancel(boolean) Cancelling}
+ * the CompleteableFuture does not affect the Process.
+ * <p>
+ * If the process is {@link #isAlive not alive} the {@link CompletableFuture}
+ * returned has been {@link java.util.concurrent.CompletableFuture#complete completed}.
+ *
+ * @return a new {@code CompletableFuture<ProcessHandle>} for the ProcessHandle
+ *
+ * @throws IllegalStateException if the process is the current process
+ */
+ CompletableFuture<ProcessHandle> onExit();
+
+ /**
+ * Returns {@code true} if the implementation of {@link #destroy}
+ * normally terminates the process.
+ * Returns {@code false} if the implementation of {@code destroy}
+ * forcibly and immediately terminates the process.
+ *
+ * @return {@code true} if the implementation of {@link #destroy}
+ * normally terminates the process;
+ * otherwise, {@link #destroy} forcibly terminates the process
+ */
+ boolean supportsNormalTermination();
+
+ /**
+ * Requests the process to be killed.
+ * Whether the process represented by this {@code ProcessHandle} object is
+ * {@link #supportsNormalTermination normally terminated} or not is
+ * implementation dependent.
+ * Forcible process destruction is defined as the immediate termination of the
+ * process, whereas normal termination allows the process to shut down cleanly.
+ * If the process is not alive, no action is taken.
+ * The operating system access controls may prevent the process
+ * from being killed.
+ * <p>
+ * The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is
+ * {@link java.util.concurrent.CompletableFuture#complete completed}
+ * when the process has terminated.
+ * <p>
+ * Note: The process may not terminate immediately.
+ * For example, {@code isAlive()} may return true for a brief period
+ * after {@code destroy()} is called.
+ *
+ * @return {@code true} if termination was successfully requested,
+ * otherwise {@code false}
+ * @throws IllegalStateException if the process is the current process
+ */
+ boolean destroy();
+
+ /**
+ * Requests the process to be killed forcibly.
+ * The process represented by this {@code ProcessHandle} object is
+ * forcibly terminated.
+ * Forcible process destruction is defined as the immediate termination of the
+ * process, whereas normal termination allows the process to shut down cleanly.
+ * If the process is not alive, no action is taken.
+ * The operating system access controls may prevent the process
+ * from being killed.
+ * <p>
+ * The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is
+ * {@link java.util.concurrent.CompletableFuture#complete completed}
+ * when the process has terminated.
+ * <p>
+ * Note: The process may not terminate immediately.
+ * For example, {@code isAlive()} may return true for a brief period
+ * after {@code destroyForcibly()} is called.
+ *
+ * @return {@code true} if termination was successfully requested,
+ * otherwise {@code false}
+ * @throws IllegalStateException if the process is the current process
+ */
+ boolean destroyForcibly();
+
+ /**
+ * Tests whether the process represented by this {@code ProcessHandle} is alive.
+ * Process termination is implementation and operating system specific.
+ * The process is considered alive as long as the PID is valid.
+ *
+ * @return {@code true} if the process represented by this
+ * {@code ProcessHandle} object has not yet terminated
+ */
+ boolean isAlive();
+
+ /**
+ * Compares this ProcessHandle with the specified ProcessHandle for order.
+ * The order is not specified, but is consistent with {@link Object#equals},
+ * which returns {@code true} if and only if two instances of ProcessHandle
+ * are of the same implementation and represent the same system process.
+ * Comparison is only supported among objects of same implementation.
+ * If attempt is made to mutually compare two different implementations
+ * of {@link ProcessHandle}s, {@link ClassCastException} is thrown.
+ *
+ * @param other the ProcessHandle to be compared
+ * @return a negative integer, zero, or a positive integer as this object
+ * is less than, equal to, or greater than the specified object.
+ * @throws NullPointerException if the specified object is null
+ * @throws ClassCastException if the specified object is not of same class
+ * as this object
+ */
+ @Override
+ int compareTo(ProcessHandle other);
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/java/lang/ProcessHandleImpl.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,528 @@
+/*
+ * Copyright (c) 2014, 2015, 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package java.lang;
+
+import java.security.PrivilegedAction;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ForkJoinPool;
+import java.util.concurrent.SynchronousQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import sun.misc.InnocuousThread;
+
+import static java.security.AccessController.doPrivileged;
+
+/**
+ * ProcessHandleImpl is the implementation of ProcessHandle.
+ *
+ * @see Process
+ * @since 1.9
+ */
+final class ProcessHandleImpl implements ProcessHandle {
+
+ /**
+ * The thread pool of "process reaper" daemon threads.
+ */
+ private static final Executor processReaperExecutor =
+ doPrivileged((PrivilegedAction<Executor>) () -> {
+
+ ThreadGroup tg = Thread.currentThread().getThreadGroup();
+ while (tg.getParent() != null) tg = tg.getParent();
+ ThreadGroup systemThreadGroup = tg;
+
+ ThreadFactory threadFactory = grimReaper -> {
+ // Our thread stack requirement is quite modest.
+ Thread t = new Thread(systemThreadGroup, grimReaper,
+ "process reaper", 32768);
+ t.setDaemon(true);
+ // A small attempt (probably futile) to avoid priority inversion
+ t.setPriority(Thread.MAX_PRIORITY);
+ return t;
+ };
+
+ return Executors.newCachedThreadPool(threadFactory);
+ });
+
+ private static class ExitCompletion extends CompletableFuture<Integer> {
+ final boolean isReaping;
+
+ ExitCompletion(boolean isReaping) {
+ this.isReaping = isReaping;
+ }
+ }
+
+ private static final ConcurrentMap<Long, ExitCompletion>
+ completions = new ConcurrentHashMap<>();
+
+ /**
+ * Returns a CompletableFuture that completes with process exit status when
+ * the process completes.
+ *
+ * @param shouldReap true if the exit value should be reaped
+ */
+ static CompletableFuture<Integer> completion(long pid, boolean shouldReap) {
+ // check canonicalizing cache 1st
+ ExitCompletion completion = completions.get(pid);
+ // re-try until we get a completion that shouldReap => isReaping
+ while (completion == null || (shouldReap && !completion.isReaping)) {
+ ExitCompletion newCompletion = new ExitCompletion(shouldReap);
+ if (completion == null) {
+ completion = completions.putIfAbsent(pid, newCompletion);
+ } else {
+ completion = completions.replace(pid, completion, newCompletion)
+ ? null : completions.get(pid);
+ }
+ if (completion == null) {
+ // newCompletion has just been installed successfully
+ completion = newCompletion;
+ // spawn a thread to wait for and deliver the exit value
+ processReaperExecutor.execute(() -> {
+ int exitValue = waitForProcessExit0(pid, shouldReap);
+ newCompletion.complete(exitValue);
+ // remove from cache afterwards
+ completions.remove(pid, newCompletion);
+ });
+ }
+ }
+ return completion;
+ }
+
+ @Override
+ public CompletableFuture<ProcessHandle> onExit() {
+ if (this.equals(current)) {
+ throw new IllegalStateException("onExit for current process not allowed");
+ }
+
+ return ProcessHandleImpl.completion(getPid(), false)
+ .handleAsync((exitStatus, unusedThrowable) -> this);
+ }
+
+ /**
+ * Wait for the process to exit, return the value.
+ * Conditionally reap the value if requested
+ * @param pid the processId
+ * @param reapvalue if true, the value is retrieved,
+ * else return the value and leave the process waitable
+ *
+ * @return the value or -1 if an error occurs
+ */
+ private static native int waitForProcessExit0(long pid, boolean reapvalue);
+
+ /**
+ * Cache the ProcessHandle of this process.
+ */
+ private static final ProcessHandleImpl current =
+ new ProcessHandleImpl(getCurrentPid0());
+
+ /**
+ * The pid of this ProcessHandle.
+ */
+ private final long pid;
+
+ /**
+ * Private constructor. Instances are created by the {@code get(long)} factory.
+ * @param pid the pid for this instance
+ */
+ private ProcessHandleImpl(long pid) {
+ this.pid = pid;
+ }
+
+ /**
+ * Returns a ProcessHandle for an existing native process.
+ *
+ * @param pid the native process identifier
+ * @return The ProcessHandle for the pid if the process is alive;
+ * or {@code null} if the process ID does not exist in the native system.
+ * @throws SecurityException if RuntimePermission("manageProcess") is not granted
+ */
+ static Optional<ProcessHandle> get(long pid) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("manageProcess"));
+ }
+ return Optional.ofNullable(isAlive0(pid) ? new ProcessHandleImpl(pid) : null);
+ }
+
+ /**
+ * Returns a ProcessHandle corresponding known to exist pid.
+ * Called from ProcessImpl, it does not perform a security check or check if the process is alive.
+ * @param pid of the known to exist process
+ * @return a ProcessHandle corresponding to an existing Process instance
+ */
+ static ProcessHandle getUnchecked(long pid) {
+ return new ProcessHandleImpl(pid);
+ }
+
+ /**
+ * Returns the native process ID.
+ * A {@code long} is used to be able to fit the system specific binary values
+ * for the process.
+ *
+ * @return the native process ID
+ */
+ @Override
+ public long getPid() {
+ return pid;
+ }
+
+ /**
+ * Returns the ProcessHandle for the current native process.
+ *
+ * @return The ProcessHandle for the OS process.
+ * @throws SecurityException if RuntimePermission("manageProcess") is not granted
+ */
+ public static ProcessHandleImpl current() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("manageProcess"));
+ }
+ return current;
+ }
+
+ /**
+ * Return the pid of the current process.
+ *
+ * @return the pid of the current process
+ */
+ private static native long getCurrentPid0();
+
+ /**
+ * Returns a ProcessHandle for the parent process.
+ *
+ * @return a ProcessHandle of the parent process; {@code null} is returned
+ * if the child process does not have a parent
+ * @throws SecurityException if permission is not granted by the
+ * security policy
+ */
+ static Optional<ProcessHandle> parent(long pid) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("manageProcess"));
+ }
+ long ppid = parent0(pid);
+ if (ppid <= 0) {
+ return Optional.empty();
+ }
+ return get(ppid);
+ }
+
+ /**
+ * Returns the parent of the native pid argument.
+ *
+ * @return the parent of the native pid; if any, otherwise -1
+ */
+ private static native long parent0(long pid);
+
+ /**
+ * Returns the number of pids filled in to the array.
+ * @param pid if {@code pid} equals zero, then all known processes are returned;
+ * otherwise only direct child process pids are returned
+ * @param pids an allocated long array to receive the pids
+ * @param ppids an allocated long array to receive the parent pids; may be null
+ * @return if greater than or equals to zero is the number of pids in the array;
+ * if greater than the length of the arrays, the arrays are too small
+ */
+ private static native int getProcessPids0(long pid, long[] pids, long[] ppids);
+
+ /**
+ * Destroy the process for this ProcessHandle.
+ * @param pid the processs ID to destroy
+ * @param force {@code true} if the process should be terminated forcibly;
+ * else {@code false} for a normal termination
+ */
+ static void destroyProcess(long pid, boolean force) {
+ destroy0(pid, force);
+ }
+
+ private static native boolean destroy0(long pid, boolean forcibly);
+
+ @Override
+ public boolean destroy() {
+ if (this.equals(current)) {
+ throw new IllegalStateException("destroy of current process not allowed");
+ }
+ return destroy0(getPid(), false);
+ }
+
+ @Override
+ public boolean destroyForcibly() {
+ if (this.equals(current)) {
+ throw new IllegalStateException("destroy of current process not allowed");
+ }
+ return destroy0(getPid(), true);
+ }
+
+
+ @Override
+ public boolean supportsNormalTermination() {
+ return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
+ }
+
+ /**
+ * Tests whether the process represented by this {@code ProcessHandle} is alive.
+ *
+ * @return {@code true} if the process represented by this
+ * {@code ProcessHandle} object has not yet terminated.
+ * @since 1.9
+ */
+ @Override
+ public boolean isAlive() {
+ return isAlive0(pid);
+ }
+
+ /**
+ * Returns true or false depending on whether the pid is alive.
+ * This must not reap the exitValue like the isAlive method above.
+ *
+ * @param pid the pid to check
+ * @return true or false
+ */
+ private static native boolean isAlive0(long pid);
+
+ @Override
+ public Optional<ProcessHandle> parent() {
+ return parent(pid);
+ }
+
+ @Override
+ public Stream<ProcessHandle> children() {
+ return children(pid);
+ }
+
+ /**
+ * Returns a Stream of the children of a process or all processes.
+ *
+ * @param pid the pid of the process for which to find the children;
+ * 0 for all processes
+ * @return a stream of ProcessHandles
+ */
+ static Stream<ProcessHandle> children(long pid) {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("manageProcess"));
+ }
+ int size = 100;
+ long[] childpids = null;
+ while (childpids == null || size > childpids.length) {
+ childpids = new long[size];
+ size = getProcessPids0(pid, childpids, null);
+ }
+ return Arrays.stream(childpids, 0, size).mapToObj((id) -> new ProcessHandleImpl(id));
+ }
+
+ @Override
+ public Stream<ProcessHandle> allChildren() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("manageProcess"));
+ }
+ int size = 100;
+ long[] pids = null;
+ long[] ppids = null;
+ while (pids == null || size > pids.length) {
+ pids = new long[size];
+ ppids = new long[size];
+ size = getProcessPids0(0, pids, ppids);
+ }
+
+ int next = 0; // index of next process to check
+ int count = -1; // count of subprocesses scanned
+ long ppid = pid; // start looking for this parent
+ do {
+ // Scan from next to size looking for ppid
+ // if found, exchange it to index next
+ for (int i = next; i < size; i++) {
+ if (ppids[i] == ppid) {
+ swap(pids, i, next);
+ swap(ppids, i, next);
+ next++;
+ }
+ }
+ ppid = pids[++count]; // pick up the next pid to scan for
+ } while (count < next);
+
+ return Arrays.stream(pids, 0, count).mapToObj((id) -> new ProcessHandleImpl(id));
+ }
+
+ // Swap two elements in an array
+ private static void swap(long[] array, int x, int y) {
+ long v = array[x];
+ array[x] = array[y];
+ array[y] = v;
+ }
+
+ @Override
+ public ProcessHandle.Info info() {
+ return ProcessHandleImpl.Info.info(pid);
+ }
+
+ @Override
+ public int compareTo(ProcessHandle other) {
+ return Long.compare(pid, ((ProcessHandleImpl) other).pid);
+ }
+
+ @Override
+ public String toString() {
+ return Long.toString(pid);
+ }
+
+ @Override
+ public int hashCode() {
+ return Long.hashCode(pid);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return (obj instanceof ProcessHandleImpl) &&
+ (pid == ((ProcessHandleImpl) obj).pid);
+ }
+
+ /**
+ * Implementation of ProcessHandle.Info.
+ * Information snapshot about a process.
+ * The attributes of a process vary by operating system and not available
+ * in all implementations. Additionally, information about other processes
+ * is limited by the operating system privileges of the process making the request.
+ * If a value is not available, either a {@code null} or {@code -1} is stored.
+ * The accessor methods return {@code null} if the value is not available.
+ */
+ static class Info implements ProcessHandle.Info {
+ static {
+ initIDs();
+ }
+
+ /**
+ * Initialization of JNI fieldIDs.
+ */
+ private static native void initIDs();
+
+ /**
+ * Fill in this Info instance with information about the native process.
+ * If values are not available the native code does not modify the field.
+ * @param pid of the native process
+ */
+ private native void info0(long pid);
+
+ String command;
+ String[] arguments;
+ long startTime;
+ long totalTime;
+ String user;
+
+ Info() {
+ command = null;
+ arguments = null;
+ startTime = -1L;
+ totalTime = -1L;
+ user = null;
+ }
+
+ /**
+ * Returns the Info object with the fields from the process.
+ * Whatever fields are provided by native are returned.
+ *
+ * @param pid the native process identifier
+ * @return ProcessHandle.Info non-null; individual fields may be null
+ * or -1 if not available.
+ */
+ public static ProcessHandle.Info info(long pid) {
+ Info info = new Info();
+ info.info0(pid);
+ return info;
+ }
+
+ @Override
+ public Optional<String> command() {
+ return Optional.ofNullable(command);
+ }
+
+ @Override
+ public Optional<String[]> arguments() {
+ return Optional.ofNullable(arguments);
+ }
+
+ @Override
+ public Optional<Instant> startInstant() {
+ return (startTime > 0)
+ ? Optional.of(Instant.ofEpochMilli(startTime))
+ : Optional.empty();
+ }
+
+ @Override
+ public Optional<Duration> totalCpuDuration() {
+ return (totalTime != -1)
+ ? Optional.of(Duration.ofNanos(totalTime))
+ : Optional.empty();
+ }
+
+ @Override
+ public Optional<String> user() {
+ return Optional.ofNullable(user);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(60);
+ sb.append('[');
+ if (user != null) {
+ sb.append("user: ");
+ sb.append(user());
+ }
+ if (command != null) {
+ if (sb.length() != 0) sb.append(", ");
+ sb.append("cmd: ");
+ sb.append(command);
+ }
+ if (arguments != null && arguments.length > 0) {
+ if (sb.length() != 0) sb.append(", ");
+ sb.append("args: ");
+ sb.append(Arrays.toString(arguments));
+ }
+ if (startTime != -1) {
+ if (sb.length() != 0) sb.append(", ");
+ sb.append("startTime: ");
+ sb.append(startInstant());
+ }
+ if (totalTime != -1) {
+ if (sb.length() != 0) sb.append(", ");
+ sb.append("totalTime: ");
+ sb.append(totalCpuDuration().toString());
+ }
+ sb.append(']');
+ return sb.toString();
+ }
+ }
+}
--- a/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java Fri May 29 10:28:28 2015 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/RuntimePermission.java Fri May 29 14:04:12 2015 -0400
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 2015, 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
@@ -333,6 +333,12 @@
* "../../../technotes/guides/plugin/developer_guide/rsa_how.html#use">
* usePolicy Permission</a>.</td>
* </tr>
+ * <tr>
+ * <td>manageProcess</td>
+ * <td>Native process termination and information about processes
+ * {@link ProcessHandle}.</td>
+ * <td>Allows code to identify and terminate processes that it did not create.</td>
+ * </tr>
*
* <tr>
* <td>localeServiceProvider</td>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/solaris/native/libjava/ProcessHandleImpl_solaris.c Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,371 @@
+/*
+ * Copyright (c) 2014, 2015, 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "jni.h"
+#include "jni_util.h"
+#include "java_lang_ProcessHandleImpl.h"
+#include "java_lang_ProcessHandleImpl_Info.h"
+
+
+#include <stdio.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <procfs.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <limits.h>
+
+/**
+ * Implementations of ProcessHandleImpl functions that are
+ * NOT common to all Unix variants:
+ * - getProcessPids0(pid, pidArray)
+ *
+ * Implementations of ProcessHandleImpl_Info
+ * - totalTime, startTime
+ * - Command
+ * - Arguments
+ */
+
+/*
+ * Signatures for internal OS specific functions.
+ */
+static pid_t parentPid(JNIEnv *env, pid_t pid);
+static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid);
+static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid);
+
+extern jstring uidToUser(JNIEnv* env, uid_t uid);
+
+/* Field id for jString 'command' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_commandID;
+
+/* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_argumentsID;
+
+/* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_totalTimeID;
+
+/* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_startTimeID;
+
+/* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */
+static jfieldID ProcessHandleImpl_Info_userID;
+
+/* static value for clock ticks per second. */
+static long clock_ticks_per_second;
+
+/**************************************************************
+ * Static method to initialize field IDs and the ticks per second rate.
+ *
+ * Class: java_lang_ProcessHandleImpl_Info
+ * Method: initIDs
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
+ (JNIEnv *env, jclass clazz) {
+
+ CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
+ clazz, "command", "Ljava/lang/String;"));
+ CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
+ clazz, "arguments", "[Ljava/lang/String;"));
+ CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
+ clazz, "totalTime", "J"));
+ CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
+ clazz, "startTime", "J"));
+ CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
+ clazz, "user", "Ljava/lang/String;"));
+ clock_ticks_per_second = sysconf(_SC_CLK_TCK);
+}
+
+/*
+ * Returns the parent pid of the requested pid.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: parent0
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
+(JNIEnv *env, jobject obj, jlong jpid) {
+ pid_t pid = (pid_t) jpid;
+ pid_t ppid = -1;
+
+ if (pid == getpid()) {
+ ppid = getppid();
+ } else {
+ ppid = parentPid(env, pid);
+ }
+ return (jlong) ppid;
+}
+
+/*
+ * Returns the children of the requested pid and optionally each parent.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: getChildPids
+ * Signature: (J[J)I
+ *
+ * Reads /proc and accumulates any process who parent pid matches.
+ * The resulting pids are stored into the array of longs.
+ * The number of pids is returned if they all fit.
+ * If the array is too short, the desired length is returned.
+ */
+JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
+(JNIEnv *env, jclass clazz, jlong jpid,
+ jlongArray jarray, jlongArray jparentArray)
+{
+ DIR* dir;
+ struct dirent* ptr;
+ pid_t pid = (pid_t) jpid;
+ size_t count = 0;
+ jlong* pids = NULL;
+ jlong* ppids = NULL;
+ size_t parentArraySize = 0;
+ size_t arraySize = 0;
+
+ arraySize = (*env)->GetArrayLength(env, jarray);
+ JNU_CHECK_EXCEPTION_RETURN(env, 0);
+ if (jparentArray != NULL) {
+ parentArraySize = (*env)->GetArrayLength(env, jparentArray);
+ JNU_CHECK_EXCEPTION_RETURN(env, 0);
+
+ if (arraySize != parentArraySize) {
+ JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
+ return 0;
+ }
+ }
+
+ /*
+ * To locate the children we scan /proc looking for files that have a
+ * positive integer as a filename.
+ */
+ if ((dir = opendir("/proc")) == NULL) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/Runtime", "Unable to open /proc");
+ return 0;
+ }
+
+ do { // Block to break out of on Exception
+ pids = (*env)->GetLongArrayElements(env, jarray, NULL);
+ if (pids == NULL) {
+ break;
+ }
+ if (jparentArray != NULL) {
+ ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
+ if (ppids == NULL) {
+ break;
+ }
+ }
+
+ while ((ptr = readdir(dir)) != NULL) {
+ pid_t ppid;
+
+ /* skip files that aren't numbers */
+ pid_t childpid = (pid_t) atoi(ptr->d_name);
+ if ((int) childpid <= 0) {
+ continue;
+ }
+
+ ppid = 0;
+ if (pid != 0 || jparentArray != NULL) {
+ // parentPid opens and reads /proc/pid/stat
+ ppid = parentPid(env, childpid);
+ }
+ if (pid == 0 || ppid == pid) {
+ if (count < arraySize) {
+ // Only store if it fits
+ pids[count] = (jlong) childpid;
+
+ if (ppids != NULL) {
+ // Store the parentPid
+ ppids[count] = (jlong) ppid;
+ }
+ }
+ count++; // Count to tabulate size needed
+ }
+ }
+ } while (0);
+
+ if (pids != NULL) {
+ (*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
+ }
+ if (ppids != NULL) {
+ (*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
+ }
+
+ closedir(dir);
+ // If more pids than array had size for; count will be greater than array size
+ return count;
+}
+
+/*
+ * Returns the parent pid of a given pid, or -1 if not found
+ */
+static pid_t parentPid(JNIEnv *env, pid_t pid) {
+ FILE* fp;
+ pstatus_t pstatus;
+ int statlen;
+ char fn[32];
+ int i, p;
+ char* s;
+
+ /*
+ * Try to open /proc/%d/status
+ */
+ snprintf(fn, sizeof fn, "/proc/%d/status", pid);
+ fp = fopen(fn, "r");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ /*
+ * The format is: pid (command) state ppid ...
+ * As the command could be anything we must find the right most
+ * ")" and then skip the white spaces that follow it.
+ */
+ statlen = fread(&pstatus, 1, (sizeof pstatus), fp);
+ fclose(fp);
+ if (statlen < 0) {
+ return -1;
+ }
+ return (pid_t) pstatus.pr_ppid;
+}
+
+/**************************************************************
+ * Implementation of ProcessHandleImpl_Info native methods.
+ */
+
+/*
+ * Fill in the Info object from the OS information about the process.
+ *
+ * Class: java_lang_ProcessHandleImpl_Info
+ * Method: info0
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
+ (JNIEnv *env, jobject jinfo, jlong jpid) {
+ pid_t pid = (pid_t) jpid;
+ getStatInfo(env, jinfo, pid);
+ getCmdlineInfo(env, jinfo, pid);
+}
+
+/**
+ * Read /proc/<pid>/stat and fill in the fields of the Info object.
+ * Gather the user and system times.
+ */
+static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
+ FILE* fp;
+ pstatus_t pstatus;
+ struct stat stat_buf;
+ int ret;
+ char fn[32];
+ int i, p;
+ char* s;
+ jlong totalTime;
+
+ /*
+ * Try to open /proc/%d/status
+ */
+ snprintf(fn, sizeof fn, "/proc/%d/status", pid);
+
+ if (stat(fn, &stat_buf) < 0) {
+ return;
+ }
+
+ fp = fopen(fn, "r");
+ if (fp == NULL) {
+ return;
+ }
+
+ ret = fread(&pstatus, 1, (sizeof pstatus), fp);
+ fclose(fp);
+ if (ret < 0) {
+ return;
+ }
+
+ totalTime = pstatus.pr_utime.tv_sec * 1000000000L + pstatus.pr_utime.tv_nsec +
+ pstatus.pr_stime.tv_sec * 1000000000L + pstatus.pr_stime.tv_nsec;
+
+ (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, totalTime);
+ JNU_CHECK_EXCEPTION(env);
+}
+
+static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
+ FILE* fp;
+ psinfo_t psinfo;
+ int ret;
+ char fn[32];
+ char exePath[PATH_MAX];
+ int i, p;
+ jlong startTime;
+ jobjectArray cmdArray;
+ jstring str = NULL;
+
+ /*
+ * try to open /proc/%d/psinfo
+ */
+ snprintf(fn, sizeof fn, "/proc/%d/psinfo", pid);
+ fp = fopen(fn, "r");
+ if (fp == NULL) {
+ return;
+ }
+
+ /*
+ * The format is: pid (command) state ppid ...
+ * As the command could be anything we must find the right most
+ * ")" and then skip the white spaces that follow it.
+ */
+ ret = fread(&psinfo, 1, (sizeof psinfo), fp);
+ fclose(fp);
+ if (ret < 0) {
+ return;
+ }
+
+ CHECK_NULL((str = uidToUser(env, psinfo.pr_uid)));
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, str);
+ JNU_CHECK_EXCEPTION(env);
+
+ startTime = (jlong)psinfo.pr_start.tv_sec * (jlong)1000 +
+ (jlong)psinfo.pr_start.tv_nsec / 1000000;
+ (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
+ JNU_CHECK_EXCEPTION(env);
+
+ /*
+ * The path to the executable command is the link in /proc/<pid>/paths/a.out.
+ */
+ snprintf(fn, sizeof fn, "/proc/%d/path/a.out", pid);
+ if ((ret = readlink(fn, exePath, PATH_MAX - 1)) < 0) {
+ return;
+ }
+
+ // null terminate and create String to store for command
+ exePath[ret] = '\0';
+ CHECK_NULL(str = JNU_NewStringPlatform(env, exePath));
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, str);
+ JNU_CHECK_EXCEPTION(env);
+}
+
--- a/jdk/src/java.base/unix/classes/java/lang/ProcessImpl.java Fri May 29 10:28:28 2015 -0700
+++ b/jdk/src/java.base/unix/classes/java/lang/ProcessImpl.java Fri May 29 14:04:12 2015 -0400
@@ -39,9 +39,7 @@
import java.util.EnumSet;
import java.util.Locale;
import java.util.Set;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Executor;
-import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.security.AccessController;
import static java.security.AccessController.doPrivileged;
@@ -50,8 +48,7 @@
import java.security.PrivilegedExceptionAction;
/**
- * This java.lang.Process subclass in the UNIX environment is for the exclusive use of
- * ProcessBuilder.start() to create new processes.
+ * java.lang.Process subclass in the UNIX environment.
*
* @author Mario Wolczko and Ross Knippel.
* @author Konstantin Kladko (ported to Linux and Bsd)
@@ -63,12 +60,16 @@
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
+ // Linux platforms support a normal (non-forcible) kill signal.
+ static final boolean SUPPORTS_NORMAL_TERMINATION = true;
+
private final int pid;
+ private final ProcessHandle processHandle;
private int exitcode;
private boolean hasExited;
private /* final */ OutputStream stdin;
- private /* final */ InputStream stdout;
+ private /* final */ InputStream stdout;
private /* final */ InputStream stderr;
// only used on Solaris
@@ -97,7 +98,7 @@
Platform(LaunchMechanism ... launchMechanisms) {
this.defaultLaunchMechanism = launchMechanisms[0];
this.validLaunchMechanisms =
- EnumSet.copyOf(Arrays.asList(launchMechanisms));
+ EnumSet.copyOf(Arrays.asList(launchMechanisms));
}
@SuppressWarnings("fallthrough")
@@ -121,43 +122,43 @@
String helperPath() {
return AccessController.doPrivileged(
- (PrivilegedAction<String>) () ->
- helperPath(System.getProperty("java.home"),
- System.getProperty("os.arch"))
+ (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(Locale.ENGLISH);
- } else {
- try {
- lm = LaunchMechanism.valueOf(
- s.toUpperCase(Locale.ENGLISH));
- } catch (IllegalArgumentException e) {
- lm = null;
- }
+ (PrivilegedAction<LaunchMechanism>) () -> {
+ String s = System.getProperty(
+ "jdk.lang.Process.launchMechanism");
+ LaunchMechanism lm;
+ if (s == null) {
+ lm = defaultLaunchMechanism;
+ s = lm.name().toLowerCase(Locale.ENGLISH);
+ } else {
+ try {
+ lm = LaunchMechanism.valueOf(
+ s.toUpperCase(Locale.ENGLISH));
+ } 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;
}
+ 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")
+ (PrivilegedAction<String>) () -> System.getProperty("os.name")
);
if (osName.equals("Linux")) { return LINUX; }
@@ -173,17 +174,14 @@
private static final LaunchMechanism launchMechanism = platform.launchMechanism();
private static final byte[] helperpath = toCString(platform.helperPath());
- /* this is for the reaping thread */
- private native int waitForProcessExit(int pid);
-
private static byte[] toCString(String s) {
if (s == null)
return null;
byte[] bytes = s.getBytes();
byte[] result = new byte[bytes.length + 1];
System.arraycopy(bytes, 0,
- result, 0,
- bytes.length);
+ result, 0,
+ bytes.length);
result[result.length-1] = (byte)0;
return result;
}
@@ -304,30 +302,7 @@
byte[] dir,
int[] fds,
boolean redirectErrorStream)
- throws IOException;
-
- /**
- * The thread pool of "process reaper" daemon threads.
- */
- private static final Executor processReaperExecutor =
- doPrivileged((PrivilegedAction<Executor>) () -> {
-
- ThreadGroup tg = Thread.currentThread().getThreadGroup();
- while (tg.getParent() != null) tg = tg.getParent();
- ThreadGroup systemThreadGroup = tg;
-
- ThreadFactory threadFactory = grimReaper -> {
- // Our thread stack requirement is quite modest.
- Thread t = new Thread(systemThreadGroup, grimReaper,
- "process reaper", 32768);
- t.setDaemon(true);
- // A small attempt (probably futile) to avoid priority inversion
- t.setPriority(Thread.MAX_PRIORITY);
- return t;
- };
-
- return Executors.newCachedThreadPool(threadFactory);
- });
+ throws IOException;
private ProcessImpl(final byte[] prog,
final byte[] argBlock, final int argc,
@@ -338,13 +313,14 @@
throws IOException {
pid = forkAndExec(launchMechanism.ordinal() + 1,
- helperpath,
- prog,
- argBlock, argc,
- envBlock, envc,
- dir,
- fds,
- redirectErrorStream);
+ helperpath,
+ prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ fds,
+ redirectErrorStream);
+ processHandle = ProcessHandleImpl.getUnchecked(pid);
try {
doPrivileged((PrivilegedExceptionAction<Void>) () -> {
@@ -371,18 +347,16 @@
new ProcessPipeOutputStream(fds[0]);
stdout = (fds[1] == -1) ?
- ProcessBuilder.NullInputStream.INSTANCE :
- new ProcessPipeInputStream(fds[1]);
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new ProcessPipeInputStream(fds[1]);
stderr = (fds[2] == -1) ?
- ProcessBuilder.NullInputStream.INSTANCE :
- new ProcessPipeInputStream(fds[2]);
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new ProcessPipeInputStream(fds[2]);
- processReaperExecutor.execute(() -> {
- int exitcode = waitForProcessExit(pid);
-
+ ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
synchronized (this) {
- this.exitcode = exitcode;
+ this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
this.hasExited = true;
this.notifyAll();
}
@@ -395,6 +369,8 @@
if (stdin instanceof ProcessPipeOutputStream)
((ProcessPipeOutputStream) stdin).processExited();
+
+ return null;
});
break;
@@ -402,18 +378,18 @@
stdin = (fds[0] == -1) ?
ProcessBuilder.NullOutputStream.INSTANCE :
new BufferedOutputStream(
- new FileOutputStream(newFileDescriptor(fds[0])));
+ new FileOutputStream(newFileDescriptor(fds[0])));
stdout = (fds[1] == -1) ?
- ProcessBuilder.NullInputStream.INSTANCE :
- new BufferedInputStream(
- stdout_inner_stream =
- new DeferredCloseInputStream(
- newFileDescriptor(fds[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]));
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new DeferredCloseInputStream(newFileDescriptor(fds[2]));
/*
* For each subprocess forked a corresponding reaper task
@@ -423,14 +399,13 @@
* exitStatus() to be safely executed in parallel (and they
* need no native code).
*/
- processReaperExecutor.execute(() -> {
- int exitcode = waitForProcessExit(pid);
-
+ ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
synchronized (this) {
- this.exitcode = exitcode;
+ this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
this.hasExited = true;
this.notifyAll();
}
+ return null;
});
break;
@@ -440,18 +415,16 @@
new ProcessPipeOutputStream(fds[0]);
stdout = (fds[1] == -1) ?
- ProcessBuilder.NullInputStream.INSTANCE :
- new DeferredCloseProcessPipeInputStream(fds[1]);
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new DeferredCloseProcessPipeInputStream(fds[1]);
stderr = (fds[2] == -1) ?
- ProcessBuilder.NullInputStream.INSTANCE :
- new DeferredCloseProcessPipeInputStream(fds[2]);
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new DeferredCloseProcessPipeInputStream(fds[2]);
- processReaperExecutor.execute(() -> {
- int exitcode = waitForProcessExit(pid);
-
+ ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
synchronized (this) {
- this.exitcode = exitcode;
+ this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
this.hasExited = true;
this.notifyAll();
}
@@ -464,6 +437,8 @@
if (stdin instanceof ProcessPipeOutputStream)
((ProcessPipeOutputStream) stdin).processExited();
+
+ return null;
});
break;
@@ -492,7 +467,7 @@
@Override
public synchronized boolean waitFor(long timeout, TimeUnit unit)
- throws InterruptedException
+ throws InterruptedException
{
long remainingNanos = unit.toNanos(timeout); // throw NPE before other conditions
if (hasExited) return true;
@@ -517,8 +492,6 @@
return exitcode;
}
- private static native void destroyProcess(int pid, boolean force);
-
private void destroy(boolean force) {
switch (platform) {
case LINUX:
@@ -532,7 +505,7 @@
// soon, so this is quite safe.
synchronized (this) {
if (!hasExited)
- destroyProcess(pid, force);
+ ProcessHandleImpl.destroyProcess(pid, force);
}
try { stdin.close(); } catch (IOException ignored) {}
try { stdout.close(); } catch (IOException ignored) {}
@@ -548,14 +521,14 @@
// soon, so this is quite safe.
synchronized (this) {
if (!hasExited)
- destroyProcess(pid, force);
+ ProcessHandleImpl.destroyProcess(pid, force);
try {
stdin.close();
if (stdout_inner_stream != null)
stdout_inner_stream.closeDeferred(stdout);
if (stderr instanceof DeferredCloseInputStream)
((DeferredCloseInputStream) stderr)
- .closeDeferred(stderr);
+ .closeDeferred(stderr);
} catch (IOException e) {
// ignore
}
@@ -566,6 +539,27 @@
}
}
+ @Override
+ public CompletableFuture<Process> onExit() {
+ return ProcessHandleImpl.completion(pid, false)
+ .handleAsync((exitStatus, unusedThrowable) -> this);
+ }
+
+ @Override
+ public ProcessHandle toHandle() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("manageProcess"));
+ }
+ return processHandle;
+ }
+
+ @Override
+ public boolean supportsNormalTermination() {
+ return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
+ }
+
+ @Override
public void destroy() {
destroy(false);
}
@@ -629,8 +623,8 @@
byte[] stragglers = drainInputStream(in);
in.close();
this.in = (stragglers == null) ?
- ProcessBuilder.NullInputStream.INSTANCE :
- new ByteArrayInputStream(stragglers);
+ ProcessBuilder.NullInputStream.INSTANCE :
+ new ByteArrayInputStream(stragglers);
}
} catch (IOException ignored) {}
}
@@ -797,7 +791,7 @@
*
*/
private static class DeferredCloseProcessPipeInputStream
- extends BufferedInputStream {
+ extends BufferedInputStream {
private final Object closeLock = new Object();
private int useCount = 0;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/unix/native/libjava/ProcessHandleImpl_unix.c Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,769 @@
+/*
+ * Copyright (c) 2014, 2015, 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+#include "jni.h"
+#include "jni_util.h"
+#include "java_lang_ProcessHandleImpl.h"
+#include "java_lang_ProcessHandleImpl_Info.h"
+
+
+#include <stdio.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <string.h>
+#include <dirent.h>
+#include <ctype.h>
+
+/**
+ * Implementations of ProcessHandleImpl functions that are common to all
+ * Unix variants:
+ * - waitForProcessExit0(pid, reap)
+ * - getCurrentPid0()
+ * - destroy0(pid, force)
+ */
+
+
+#ifndef WIFEXITED
+#define WIFEXITED(status) (((status)&0xFF) == 0)
+#endif
+
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(status) (((status)>>8)&0xFF)
+#endif
+
+#ifndef WIFSIGNALED
+#define WIFSIGNALED(status) (((status)&0xFF) > 0 && ((status)&0xFF00) == 0)
+#endif
+
+#ifndef WTERMSIG
+#define WTERMSIG(status) ((status)&0x7F)
+#endif
+
+#define RESTARTABLE(_cmd, _result) do { \
+ do { \
+ _result = _cmd; \
+ } while((_result == -1) && (errno == EINTR)); \
+} while(0)
+
+#define RESTARTABLE_RETURN_PTR(_cmd, _result) do { \
+ do { \
+ _result = _cmd; \
+ } while((_result == NULL) && (errno == EINTR)); \
+} while(0)
+
+
+/* Block until a child process exits and return its exit code.
+ * Note, can only be called once for any given pid if reapStatus = true.
+ */
+JNIEXPORT jint JNICALL
+Java_java_lang_ProcessHandleImpl_waitForProcessExit0(JNIEnv* env,
+ jclass junk,
+ jlong jpid,
+ jboolean reapStatus)
+{
+ pid_t pid = (pid_t)jpid;
+ errno = 0;
+
+ if (reapStatus != JNI_FALSE) {
+ /* Wait for the child process to exit.
+ * waitpid() is standard, so use it on all POSIX platforms.
+ * It is known to work when blocking to wait for the pid
+ * This returns immediately if the child has already exited.
+ */
+ int status;
+ while (waitpid(pid, &status, 0) < 0) {
+ switch (errno) {
+ case ECHILD: return 0;
+ case EINTR: break;
+ default: return -1;
+ }
+ }
+
+ if (WIFEXITED(status)) {
+ return WEXITSTATUS(status);
+ } else if (WIFSIGNALED(status)) {
+ /* The child exited because of a signal.
+ * The best value to return is 0x80 + signal number,
+ * because that is what all Unix shells do, and because
+ * it allows callers to distinguish between process exit and
+ * process death by signal.
+ * Unfortunately, the historical behavior on Solaris is to return
+ * the signal number, and we preserve this for compatibility. */
+#ifdef __solaris__
+ return WTERMSIG(status);
+#else
+ return 0x80 + WTERMSIG(status);
+#endif
+ } else {
+ return status;
+ }
+ } else {
+ /*
+ * Wait for the child process to exit without reaping the exitValue.
+ * waitid() is standard on all POSIX platforms.
+ * Note: waitid on Mac OS X 10.7 seems to be broken;
+ * it does not return the exit status consistently.
+ */
+ siginfo_t siginfo;
+ int options = WEXITED | WNOWAIT;
+ memset(&siginfo, 0, sizeof siginfo);
+ while (waitid(P_PID, pid, &siginfo, options) < 0) {
+ switch (errno) {
+ case ECHILD: return 0;
+ case EINTR: break;
+ default: return -1;
+ }
+ }
+
+ if (siginfo.si_code == CLD_EXITED) {
+ /*
+ * The child exited normally; get its exit code.
+ */
+ return siginfo.si_status;
+ } else if (siginfo.si_code == CLD_KILLED || siginfo.si_code == CLD_DUMPED) {
+ /* The child exited because of a signal.
+ * The best value to return is 0x80 + signal number,
+ * because that is what all Unix shells do, and because
+ * it allows callers to distinguish between process exit and
+ * process death by signal.
+ * Unfortunately, the historical behavior on Solaris is to return
+ * the signal number, and we preserve this for compatibility. */
+ #ifdef __solaris__
+ return WTERMSIG(siginfo.si_status);
+ #else
+ return 0x80 + WTERMSIG(siginfo.si_status);
+ #endif
+ } else {
+ /*
+ * Unknown exit code; pass it through.
+ */
+ return siginfo.si_status;
+ }
+ }
+}
+
+/*
+ * Class: java_lang_ProcessHandleImpl
+ * Method: getCurrentPid0
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_getCurrentPid0
+(JNIEnv *env, jclass clazz) {
+ pid_t pid = getpid();
+ return (jlong) pid;
+}
+
+/*
+ * Class: java_lang_ProcessHandleImpl
+ * Method: isAlive0
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_java_lang_ProcessHandleImpl_isAlive0
+(JNIEnv *env, jobject obj, jlong jpid) {
+ pid_t pid = (pid_t) jpid;
+ return (kill(pid, 0) < 0) ? JNI_FALSE : JNI_TRUE;
+}
+
+/*
+ * Class: java_lang_ProcessHandleImpl
+ * Method: destroy0
+ * Signature: (Z)Z
+ */
+JNIEXPORT jboolean JNICALL Java_java_lang_ProcessHandleImpl_destroy0
+(JNIEnv *env, jobject obj, jlong jpid, jboolean force) {
+ pid_t pid = (pid_t) jpid;
+ int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM;
+ return (kill(pid, sig) >= 0);
+
+}
+
+/**
+ * Size of password or group entry when not available via sysconf
+ */
+#define ENT_BUF_SIZE 1024
+
+/**
+ * Return a strong username for the uid_t or null.
+ */
+jstring uidToUser(JNIEnv* env, uid_t uid) {
+ int result = 0;
+ int buflen;
+ char* pwbuf;
+ jstring name = NULL;
+
+ /* allocate buffer for password record */
+ buflen = (int)sysconf(_SC_GETPW_R_SIZE_MAX);
+ if (buflen == -1)
+ buflen = ENT_BUF_SIZE;
+ pwbuf = (char*)malloc(buflen);
+ if (pwbuf == NULL) {
+ JNU_ThrowOutOfMemoryError(env, "Unable to open getpwent");
+ } else {
+ struct passwd pwent;
+ struct passwd* p = NULL;
+
+#ifdef __solaris__
+ RESTARTABLE_RETURN_PTR(getpwuid_r(uid, &pwent, pwbuf, (size_t)buflen), p);
+#else
+ RESTARTABLE(getpwuid_r(uid, &pwent, pwbuf, (size_t)buflen, &p), result);
+#endif
+
+ // Return the Java String if a name was found
+ if (result == 0 && p != NULL &&
+ p->pw_name != NULL && *(p->pw_name) != '\0') {
+ name = JNU_NewStringPlatform(env, p->pw_name);
+ }
+ free(pwbuf);
+ }
+ return name;
+}
+
+/**
+ * Implementations of ProcessHandleImpl functions that are common to
+ * (some) Unix variants:
+ * - getProcessPids0(pid, pidArray, parentArray)
+ */
+
+#if defined(__linux__) || defined(__AIX__)
+
+/*
+ * Signatures for internal OS specific functions.
+ */
+static pid_t parentPid(JNIEnv *env, pid_t pid);
+static jint getChildren(JNIEnv *env, jlong jpid,
+ jlongArray array, jlongArray jparentArray);
+
+static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid);
+static void getCmdlineInfo(JNIEnv *env, pid_t pid, jobject jinfo);
+static long long getBoottime(JNIEnv *env);
+
+jstring uidToUser(JNIEnv* env, uid_t uid);
+
+/* Field id for jString 'command' in java.lang.ProcessHandleImpl.Info */
+static jfieldID ProcessHandleImpl_Info_commandID;
+
+/* Field id for jString[] 'arguments' in java.lang.ProcessHandleImpl.Info */
+static jfieldID ProcessHandleImpl_Info_argumentsID;
+
+/* Field id for jlong 'totalTime' in java.lang.ProcessHandleImpl.Info */
+static jfieldID ProcessHandleImpl_Info_totalTimeID;
+
+/* Field id for jlong 'startTime' in java.lang.ProcessHandleImpl.Info */
+static jfieldID ProcessHandleImpl_Info_startTimeID;
+
+/* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */
+static jfieldID ProcessHandleImpl_Info_userID;
+
+/* static value for clock ticks per second. */
+static long clock_ticks_per_second;
+
+/* A static offset in milliseconds since boot. */
+static long long bootTime_ms;
+
+/**************************************************************
+ * Static method to initialize field IDs and the ticks per second rate.
+ *
+ * Class: java_lang_ProcessHandleImpl_Info
+ * Method: initIDs
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
+ (JNIEnv *env, jclass clazz) {
+
+ CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
+ clazz, "command", "Ljava/lang/String;"));
+ CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
+ clazz, "arguments", "[Ljava/lang/String;"));
+ CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
+ clazz, "totalTime", "J"));
+ CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
+ clazz, "startTime", "J"));
+ CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
+ clazz, "user", "Ljava/lang/String;"));
+ clock_ticks_per_second = sysconf(_SC_CLK_TCK);
+ bootTime_ms = getBoottime(env);
+}
+
+/*
+ * Returns the parent pid of the requested pid.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: parent0
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
+(JNIEnv *env, jobject obj, jlong jpid) {
+ pid_t pid = (pid_t) jpid;
+ pid_t ppid = -1;
+
+ pid_t mypid = getpid();
+ if (pid == mypid) {
+ ppid = getppid();
+ } else {
+ ppid = parentPid(env, pid);
+ }
+ return (jlong) ppid;
+}
+
+/*
+ * Returns the children of the requested pid and optionally each parent.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: getChildPids
+ * Signature: (J[J[J)I
+ */
+JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
+(JNIEnv *env, jclass clazz, jlong jpid,
+ jlongArray jarray, jlongArray jparentArray) {
+ return getChildren(env, jpid, jarray, jparentArray);
+}
+
+/*
+ * Reads /proc and accumulates any process who parent pid matches.
+ * The resulting pids are stored into the array of longs.
+ * The number of pids is returned if they all fit.
+ * If the array is too short, the negative of the desired length is returned.
+ */
+static jint getChildren(JNIEnv *env, jlong jpid,
+ jlongArray jarray, jlongArray jparentArray) {
+ DIR* dir;
+ struct dirent* ptr;
+ pid_t pid = (pid_t) jpid;
+ pid_t ppid = 0;
+ size_t count = 0;
+ jlong* pids = NULL;
+ jlong* ppids = NULL;
+ size_t parentArraySize = 0;
+ size_t arraySize = 0;
+
+ arraySize = (*env)->GetArrayLength(env, jarray);
+ JNU_CHECK_EXCEPTION_RETURN(env, -1);
+ if (jparentArray != NULL) {
+ parentArraySize = (*env)->GetArrayLength(env, jparentArray);
+ JNU_CHECK_EXCEPTION_RETURN(env, -1);
+
+ if (arraySize != parentArraySize) {
+ JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
+ return 0;
+ }
+ }
+
+ /*
+ * To locate the children we scan /proc looking for files that have a
+ * position integer as a filename.
+ */
+ if ((dir = opendir("/proc")) == NULL) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/Runtime", "Unable to open /proc");
+ return -1;
+ }
+
+ do { // Block to break out of on Exception
+ pids = (*env)->GetLongArrayElements(env, jarray, NULL);
+ if (pids == NULL) {
+ break;
+ }
+ if (jparentArray != NULL) {
+ ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
+ if (ppids == NULL) {
+ break;
+ }
+ }
+
+ while ((ptr = readdir(dir)) != NULL) {
+ /* skip files that aren't numbers */
+ pid_t childpid = (pid_t) atoi(ptr->d_name);
+ if ((int) childpid <= 0) {
+ continue;
+ }
+
+ ppid = 0;
+ if (pid != 0 || jparentArray != NULL) {
+ // parentPid opens and reads /proc/pid/stat
+ ppid = parentPid(env, childpid);
+ }
+ if (pid == 0 || ppid == pid) {
+ if (count < arraySize) {
+ // Only store if it fits
+ pids[count] = (jlong) childpid;
+
+ if (ppids != NULL) {
+ // Store the parentPid
+ ppids[count] = (jlong) ppid;
+ }
+ }
+ count++; // Count to tabulate size needed
+ }
+ }
+ } while (0);
+
+ if (pids != NULL) {
+ (*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
+ }
+ if (ppids != NULL) {
+ (*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
+ }
+
+ closedir(dir);
+ // If more pids than array had size for; count will be greater than array size
+ return count;
+}
+
+/*
+ * Returns the parent pid of a given pid, or -1 if not found
+ */
+static pid_t parentPid(JNIEnv *env, pid_t pid) {
+ char state;
+ FILE* fp;
+ char stat[2048];
+ int statlen;
+ char fn[32];
+ int i, p;
+ char* s;
+
+ /*
+ * try to open /proc/%d/stat
+ */
+ snprintf(fn, sizeof fn, "/proc/%d/stat", pid);
+ fp = fopen(fn, "r");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ /*
+ * The format is: pid (command) state ppid ...
+ * As the command could be anything we must find the right most
+ * ")" and then skip the white spaces that follow it.
+ */
+ statlen = fread(stat, 1, (sizeof stat - 1), fp);
+ fclose(fp);
+ if (statlen < 0) {
+ return -1;
+ }
+
+ stat[statlen] = '\0';
+ s = strrchr(stat, ')');
+ if (s == NULL) {
+ return -1;
+ }
+ do s++; while (isspace(*s));
+ i = sscanf(s, "%c %d", &state, &p);
+ if (i != 2) {
+ return (pid_t)-1;
+ }
+ return (pid_t) p;
+}
+
+/**************************************************************
+ * Implementation of ProcessHandleImpl_Info native methods.
+ */
+
+/*
+ * Fill in the Info object from the OS information about the process.
+ *
+ * Class: java_lang_ProcessHandleImpl_Info
+ * Method: info0
+ * Signature: (JLjava/lang/ProcessHandle/Info;)I
+ */
+JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
+ (JNIEnv *env, jobject jinfo, jlong jpid) {
+ pid_t pid = (pid_t) jpid;
+ getStatInfo(env, jinfo, (pid_t)pid);
+ getCmdlineInfo(env, pid, jinfo);
+}
+
+/**
+ * Read /proc/<pid>/stat and fill in the fields of the Info object.
+ * The executable name, plus the user, system, and start times are gathered.
+ */
+static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
+ char state;
+ FILE* fp;
+ char buffer[2048];
+ struct stat stat_buf;
+ int statlen;
+ char fn[32];
+ int i, ppid = -2;
+ char* s;
+ char *cmd;
+ jstring name = NULL;
+ unsigned long userTime = 0; // clock tics
+ unsigned long totalTime = 0; // clock tics
+ jlong total = 0; // nano seconds
+ unsigned long long startTime = 0; // microseconds
+
+ /*
+ * Try to stat and then open /proc/%d/stat
+ */
+ snprintf(fn, sizeof fn, "/proc/%d/stat", pid);
+
+ if (stat(fn, &stat_buf) < 0) {
+ return;
+ }
+
+ CHECK_NULL((name = uidToUser(env, stat_buf.st_uid)));
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, name);
+ JNU_CHECK_EXCEPTION(env);
+
+ fp = fopen(fn, "r");
+ if (fp == NULL) {
+ return;
+ }
+
+ /*
+ * The format is: pid (command) state ppid ...
+ * As the command could be anything we must find the right most
+ * ")" and then skip the white spaces that follow it.
+ */
+ statlen = fread(buffer, 1, (sizeof buffer - 1), fp);
+ fclose(fp);
+ if (statlen < 0) {
+ return;
+ }
+
+ buffer[statlen] = '\0';
+ s = strchr(buffer, '(');
+ if (s == NULL) {
+ return;
+ }
+ // Found start of command, skip to end
+ s++;
+ s = strrchr(s, ')');
+ if (s == NULL) {
+ return;
+ }
+ s++;
+
+ // Scan the needed fields from status, retaining only ppid(4),
+ // utime (14), stime(15), starttime(22)
+ i = sscanf(s, " %c %d %*d %*d %*d %*d %*d %*u %*u %*u %*u %lu %lu %*d %*d %*d %*d %*d %*d %llu",
+ &state, &ppid, &userTime, &totalTime, &startTime);
+ if (i != 5) {
+ return; // not all values parsed; return error
+ }
+
+ total = (userTime + totalTime) * (jlong)(1000000000 / clock_ticks_per_second);
+
+ startTime = bootTime_ms + ((startTime * 1000) / clock_ticks_per_second);
+
+ (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, total);
+ JNU_CHECK_EXCEPTION(env);
+ (*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
+ JNU_CHECK_EXCEPTION(env);
+}
+
+/**
+ * Construct the argument array by parsing the arguments from the sequence
+ * of arguments. The zero'th arg is the command executable
+ */
+static int fillArgArray(JNIEnv *env, jobject jinfo,
+ int nargs, char *cp, char *argsEnd, jstring cmdexe) {
+ jobject argsArray;
+ int i;
+
+ if (nargs < 1) {
+ return 0;
+ }
+
+ if (cmdexe == NULL) {
+ // Create a string from arg[0]
+ CHECK_NULL_RETURN((cmdexe = JNU_NewStringPlatform(env, cp)), -1);
+ }
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, cmdexe);
+ JNU_CHECK_EXCEPTION_RETURN(env, -3);
+
+ // Create a String array for nargs-1 elements
+ argsArray = (*env)->NewObjectArray(env, nargs - 1, JNU_ClassString(env), NULL);
+ CHECK_NULL_RETURN(argsArray, -1);
+
+ for (i = 0; i < nargs - 1; i++) {
+ jstring str = NULL;
+
+ cp += strnlen(cp, (argsEnd - cp)) + 1;
+ if (cp > argsEnd || *cp == '\0') {
+ return -2; // Off the end pointer or an empty argument is an error
+ }
+
+ CHECK_NULL_RETURN((str = JNU_NewStringPlatform(env, cp)), -1);
+
+ (*env)->SetObjectArrayElement(env, argsArray, i, str);
+ JNU_CHECK_EXCEPTION_RETURN(env, -3);
+ }
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_argumentsID, argsArray);
+ JNU_CHECK_EXCEPTION_RETURN(env, -4);
+ return 0;
+}
+
+
+static void getCmdlineInfo(JNIEnv *env, pid_t pid, jobject jinfo) {
+ int fd;
+ int cmdlen = 0;
+ char *cmdline = NULL, *cmdEnd; // used for command line args and exe
+ jstring cmdexe = NULL;
+ char fn[32];
+
+ /*
+ * Try to open /proc/%d/cmdline
+ */
+ snprintf(fn, sizeof fn, "/proc/%d/cmdline", pid);
+ if ((fd = open(fn, O_RDONLY)) < 0) {
+ return;
+ }
+
+ do { // Block to break out of on errors
+ int i;
+ char *s;
+
+ cmdline = (char*)malloc(PATH_MAX);
+ if (cmdline == NULL) {
+ break;
+ }
+
+ /*
+ * The path to the executable command is the link in /proc/<pid>/exe.
+ */
+ snprintf(fn, sizeof fn, "/proc/%d/exe", pid);
+ if ((cmdlen = readlink(fn, cmdline, PATH_MAX - 1)) > 0) {
+ // null terminate and create String to store for command
+ cmdline[cmdlen] = '\0';
+ cmdexe = JNU_NewStringPlatform(env, cmdline);
+ (*env)->ExceptionClear(env); // unconditionally clear any exception
+ }
+
+ /*
+ * The buffer format is the arguments nul terminated with an extra nul.
+ */
+ cmdlen = read(fd, cmdline, PATH_MAX-1);
+ if (cmdlen < 0) {
+ break;
+ }
+
+ // Terminate the buffer and count the arguments
+ cmdline[cmdlen] = '\0';
+ cmdEnd = &cmdline[cmdlen + 1];
+ for (s = cmdline,i = 0; *s != '\0' && (s < cmdEnd); i++) {
+ s += strnlen(s, (cmdEnd - s)) + 1;
+ }
+
+ if (fillArgArray(env, jinfo, i, cmdline, cmdEnd, cmdexe) < 0) {
+ break;
+ }
+ } while (0);
+
+ if (cmdline != NULL) {
+ free(cmdline);
+ }
+ if (fd >= 0) {
+ close(fd);
+ }
+}
+
+/**
+ * Read the boottime from /proc/stat.
+ */
+static long long getBoottime(JNIEnv *env) {
+ FILE *fp;
+ char *line = NULL;
+ size_t len = 0;
+ long long bootTime = 0;
+
+ fp = fopen("/proc/stat", "r");
+ if (fp == NULL) {
+ return -1;
+ }
+
+ while (getline(&line, &len, fp) != -1) {
+ if (sscanf(line, "btime %llu", &bootTime) == 1) {
+ break;
+ }
+ }
+ free(line);
+
+ if (fp != 0) {
+ fclose(fp);
+ }
+
+ return bootTime * 1000;
+}
+
+#endif // defined(__linux__) || defined(__AIX__)
+
+
+/* Block until a child process exits and return its exit code.
+ Note, can only be called once for any given pid. */
+JNIEXPORT jint JNICALL
+Java_java_lang_ProcessImpl_waitForProcessExit(JNIEnv* env,
+ jobject junk,
+ jint pid)
+{
+ /* We used to use waitid() on Solaris, waitpid() on Linux, but
+ * waitpid() is more standard, so use it on all POSIX platforms. */
+ int status;
+ /* Wait for the child process to exit. This returns immediately if
+ the child has already exited. */
+ while (waitpid(pid, &status, 0) < 0) {
+ switch (errno) {
+ case ECHILD: return 0;
+ case EINTR: break;
+ default: return -1;
+ }
+ }
+
+ if (WIFEXITED(status)) {
+ /*
+ * The child exited normally; get its exit code.
+ */
+ return WEXITSTATUS(status);
+ } else if (WIFSIGNALED(status)) {
+ /* The child exited because of a signal.
+ * The best value to return is 0x80 + signal number,
+ * because that is what all Unix shells do, and because
+ * it allows callers to distinguish between process exit and
+ * process death by signal.
+ * Unfortunately, the historical behavior on Solaris is to return
+ * the signal number, and we preserve this for compatibility. */
+#ifdef __solaris__
+ return WTERMSIG(status);
+#else
+ return 0x80 + WTERMSIG(status);
+#endif
+ } else {
+ /*
+ * Unknown exit code; pass it through.
+ */
+ return status;
+ }
+}
+
+
--- a/jdk/src/java.base/unix/native/libjava/ProcessImpl_md.c Fri May 29 10:28:28 2015 -0700
+++ b/jdk/src/java.base/unix/native/libjava/ProcessImpl_md.c Fri May 29 14:04:12 2015 -0400
@@ -226,52 +226,6 @@
#define WTERMSIG(status) ((status)&0x7F)
#endif
-/* Block until a child process exits and return its exit code.
- Note, can only be called once for any given pid. */
-JNIEXPORT jint JNICALL
-Java_java_lang_ProcessImpl_waitForProcessExit(JNIEnv* env,
- jobject junk,
- jint pid)
-{
- /* We used to use waitid() on Solaris, waitpid() on Linux, but
- * waitpid() is more standard, so use it on all POSIX platforms. */
- int status;
- /* Wait for the child process to exit. This returns immediately if
- the child has already exited. */
- while (waitpid(pid, &status, 0) < 0) {
- switch (errno) {
- case ECHILD: return 0;
- case EINTR: break;
- default: return -1;
- }
- }
-
- if (WIFEXITED(status)) {
- /*
- * The child exited normally; get its exit code.
- */
- return WEXITSTATUS(status);
- } else if (WIFSIGNALED(status)) {
- /* The child exited because of a signal.
- * The best value to return is 0x80 + signal number,
- * because that is what all Unix shells do, and because
- * it allows callers to distinguish between process exit and
- * process death by signal.
- * Unfortunately, the historical behavior on Solaris is to return
- * the signal number, and we preserve this for compatibility. */
-#ifdef __solaris__
- return WTERMSIG(status);
-#else
- return 0x80 + WTERMSIG(status);
-#endif
- } else {
- /*
- * Unknown exit code; pass it through.
- */
- return status;
- }
-}
-
static const char *
getBytes(JNIEnv *env, jbyteArray arr)
{
@@ -686,12 +640,3 @@
goto Finally;
}
-JNIEXPORT void JNICALL
-Java_java_lang_ProcessImpl_destroyProcess(JNIEnv *env,
- jobject junk,
- jint pid,
- jboolean force)
-{
- int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM;
- kill(pid, sig);
-}
--- a/jdk/src/java.base/windows/classes/java/lang/ProcessImpl.java Fri May 29 10:28:28 2015 -0700
+++ b/jdk/src/java.base/windows/classes/java/lang/ProcessImpl.java Fri May 29 14:04:12 2015 -0400
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 1995, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1995, 2015, 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
@@ -34,10 +34,12 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.lang.Override;
import java.lang.ProcessBuilder.Redirect;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -53,6 +55,9 @@
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
+ // Windows platforms support a forcible kill signal.
+ static final boolean SUPPORTS_NORMAL_TERMINATION = false;
+
/**
* Open a file for writing. If {@code append} is {@code true} then the file
* is opened for atomic append directly and a FileOutputStream constructed
@@ -306,7 +311,8 @@
}
- private long handle = 0;
+ private final long handle;
+ private final ProcessHandle processHandle;
private OutputStream stdin_stream;
private InputStream stdout_stream;
private InputStream stderr_stream;
@@ -385,6 +391,7 @@
handle = create(cmdstr, envblock, path,
stdHandles, redirectErrorStream);
+ processHandle = ProcessHandleImpl.getUnchecked(getProcessId0(handle));
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
@@ -481,7 +488,30 @@
private static native void waitForTimeoutInterruptibly(
long handle, long timeout);
- public void destroy() { terminateProcess(handle); }
+ @Override
+ public void destroy() {
+ terminateProcess(handle);
+ }
+
+ @Override
+ public CompletableFuture<Process> onExit() {
+ return ProcessHandleImpl.completion(getPid(), false)
+ .handleAsync((exitStatus, unusedThrowable) -> this);
+ }
+
+ @Override
+ public ProcessHandle toHandle() {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null) {
+ sm.checkPermission(new RuntimePermission("manageProcess"));
+ }
+ return processHandle;
+ }
+
+ @Override
+ public boolean supportsNormalTermination() {
+ return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
+ }
@Override
public Process destroyForcibly() {
@@ -493,8 +523,7 @@
@Override
public long getPid() {
- int pid = getProcessId0(handle);
- return pid;
+ return processHandle.getPid();
}
private static native int getProcessId0(long handle);
@@ -538,7 +567,7 @@
* Opens a file for atomic append. The file is created if it doesn't
* already exist.
*
- * @param file the file to open or create
+ * @param path the file to open or create
* @return the native HANDLE
*/
private static native long openForAtomicAppend(String path)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/windows/native/libjava/ProcessHandleImpl_win.c Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,426 @@
+/*
+ * Copyright (c) 2014, 2015, 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+#include "jni.h"
+#include "jvm.h"
+#include "jni_util.h"
+#include "java_lang_ProcessHandleImpl.h"
+#include "java_lang_ProcessHandleImpl_Info.h"
+
+#include <windows.h>
+#include <tlhelp32.h>
+
+static void getStatInfo(JNIEnv *env, HANDLE handle, jobject jinfo);
+static void getCmdlineInfo(JNIEnv *env, HANDLE handle, jobject jinfo);
+static void procToUser( JNIEnv *env, HANDLE handle, jobject jinfo);
+
+/**************************************************************
+ * Implementation of ProcessHandleImpl_Info native methods.
+ */
+
+/* Field id for jString 'command' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_commandID;
+
+/* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_argumentsID;
+
+/* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_totalTimeID;
+
+/* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
+static jfieldID ProcessHandleImpl_Info_startTimeID;
+
+/* Field id for jString 'accountName' in java.lang.ProcessHandleImpl.UserPrincipal */
+static jfieldID ProcessHandleImpl_Info_userID;
+
+/**************************************************************
+ * Static method to initialize field IDs.
+ *
+ * Class: java_lang_ProcessHandleImpl_Info
+ * Method: initIDs
+ * Signature: ()V
+ */
+JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
+ (JNIEnv *env, jclass clazz) {
+
+ CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
+ clazz, "command", "Ljava/lang/String;"));
+ CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
+ clazz, "arguments", "[Ljava/lang/String;"));
+ CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
+ clazz, "totalTime", "J"));
+ CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
+ clazz, "startTime", "J"));
+ CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
+ clazz, "user", "Ljava/lang/String;"));
+}
+
+/*
+ * Block until a child process exits and return its exit code.
+ */
+JNIEXPORT jint JNICALL
+Java_java_lang_ProcessHandleImpl_waitForProcessExit0(JNIEnv* env,
+ jclass junk,
+ jlong jpid,
+ jboolean reapStatus) {
+ DWORD pid = (DWORD)jpid;
+ DWORD exitValue = -1;
+ HANDLE handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION,
+ FALSE, pid);
+ if (handle == NULL) {
+ return exitValue; // No process with that pid is alive
+ }
+ do {
+ if (!GetExitCodeProcess(handle, &exitValue)) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/Runtime", "GetExitCodeProcess");
+ break;
+ }
+ if (exitValue == STILL_ACTIVE) {
+ HANDLE events[2];
+ events[0] = handle;
+ events[1] = JVM_GetThreadInterruptEvent();
+
+ if (WaitForMultipleObjects(sizeof(events)/sizeof(events[0]), events,
+ FALSE, /* Wait for ANY event */
+ INFINITE) /* Wait forever */
+ == WAIT_FAILED) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/Runtime", "WaitForMultipleObjects");
+ break;
+ }
+ }
+ } while (exitValue == STILL_ACTIVE);
+ CloseHandle(handle); // Ignore return code
+ return exitValue;
+}
+
+/*
+ * Returns the pid of the caller.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: getCurrentPid0
+ * Signature: ()J
+ */
+JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_getCurrentPid0
+(JNIEnv *env, jclass clazz) {
+ DWORD pid = GetCurrentProcessId();
+ return (jlong)pid;
+}
+
+/*
+ * Returns the parent pid of the requested pid.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: parent0
+ * Signature: (J)J
+ */
+JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
+(JNIEnv *env, jclass clazz, jlong jpid) {
+
+ DWORD ppid = -1;
+ DWORD wpid = (DWORD)jpid;
+ PROCESSENTRY32 pe32;
+ HANDLE hProcessSnap;
+
+ // Take a snapshot of all processes in the system.
+ hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hProcessSnap == INVALID_HANDLE_VALUE) {
+ JNU_ThrowByName(env,
+ "java/lang/RuntimeException", "snapshot not available");
+ return -1;
+ }
+
+ // Retrieve information about the first process,
+ pe32.dwSize = sizeof (PROCESSENTRY32);
+ if (Process32First(hProcessSnap, &pe32)) {
+ // Now walk the snapshot of processes, and
+ do {
+ if (wpid == pe32.th32ProcessID) {
+ ppid = pe32.th32ParentProcessID;
+ break;
+ }
+ } while (Process32Next(hProcessSnap, &pe32));
+ } else {
+ JNU_ThrowByName(env,
+ "java/lang/RuntimeException", "snapshot not available");
+ return -1;
+ }
+ CloseHandle(hProcessSnap); // Ignore return code
+ return (jlong)ppid;
+}
+
+/*
+ * Returns the children of the requested pid and optionally each parent.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: getChildPids
+ * Signature: (J[J[J)I
+ */
+JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
+(JNIEnv *env, jclass clazz, jlong jpid,
+ jlongArray jarray, jlongArray jparentArray) {
+
+ HANDLE hProcessSnap;
+ PROCESSENTRY32 pe32;
+ DWORD ppid = (DWORD)jpid;
+ size_t count = 0;
+ jlong* pids = NULL;
+ jlong* ppids = NULL;
+ size_t parentArraySize = 0;
+ size_t arraySize = 0;
+
+ arraySize = (*env)->GetArrayLength(env, jarray);
+ JNU_CHECK_EXCEPTION_RETURN(env, -1);
+ if (jparentArray != NULL) {
+ parentArraySize = (*env)->GetArrayLength(env, jparentArray);
+ JNU_CHECK_EXCEPTION_RETURN(env, -1);
+
+ if (arraySize != parentArraySize) {
+ JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
+ return 0;
+ }
+ }
+
+ // Take a snapshot of all processes in the system.
+ hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+ if (hProcessSnap == INVALID_HANDLE_VALUE) {
+ JNU_ThrowByName(env,
+ "java/lang/RuntimeException", "snapshot not available");
+ return 0;
+ }
+
+ // Retrieve information about the first process,
+ pe32.dwSize = sizeof (PROCESSENTRY32);
+ if (Process32First(hProcessSnap, &pe32)) {
+ do { // Block to break out of on Exception
+ pids = (*env)->GetLongArrayElements(env, jarray, NULL);
+ if (pids == NULL) {
+ break;
+ }
+ if (jparentArray != NULL) {
+ ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
+ if (ppids == NULL) {
+ break;
+ }
+ }
+ // Now walk the snapshot of processes, and
+ // save information about each process in turn
+ do {
+ if (ppid == 0 ||
+ (pe32.th32ParentProcessID > 0
+ && (pe32.th32ParentProcessID == ppid))) {
+ if (count < arraySize) {
+ // Only store if it fits
+ pids[count] = (jlong)pe32.th32ProcessID;
+ if (ppids != NULL) {
+ // Store the parentPid
+ ppids[count] = (jlong) pe32.th32ParentProcessID;
+ }
+ }
+ count++; // Count to tabulate size needed
+ }
+ } while (Process32Next(hProcessSnap, &pe32));
+ } while (0);
+
+ if (pids != NULL) {
+ (*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
+ }
+ if (ppids != NULL) {
+ (*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
+ }
+ } else {
+ JNU_ThrowByName(env,
+ "java/lang/RuntimeException", "snapshot not available");
+ return 0;
+ }
+ CloseHandle(hProcessSnap);
+ // If more pids than array had size for; count will be greater than array size
+ return (jint)count;
+}
+
+/*
+ * Destroy the process.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: destroy0
+ * Signature: (Z)V
+ */
+JNIEXPORT jboolean JNICALL Java_java_lang_ProcessHandleImpl_destroy0
+(JNIEnv *env, jclass clazz, jlong jpid, jboolean force) {
+ DWORD pid = (DWORD)jpid;
+ HANDLE handle = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
+ if (handle != NULL) {
+ TerminateProcess(handle, 1);
+ CloseHandle(handle); // Ignore return code
+ return JNI_TRUE;
+ }
+ return JNI_FALSE;
+}
+
+/*
+ * Class: java_lang_ProcessHandleImpl
+ * Method: isAlive0
+ * Signature: (J)Z
+ */
+JNIEXPORT jboolean JNICALL Java_java_lang_ProcessHandleImpl_isAlive0
+(JNIEnv *env, jclass clazz, jlong jpid) {
+ DWORD pid = (DWORD)jpid;
+
+ jboolean ret = JNI_FALSE;
+ HANDLE handle =
+ OpenProcess(THREAD_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION,
+ FALSE, pid);
+ if (handle != NULL) {
+ DWORD dwExitStatus;
+
+ GetExitCodeProcess(handle, &dwExitStatus);
+ CloseHandle(handle); // Ignore return code
+ ret = (dwExitStatus == STILL_ACTIVE);
+ }
+ return ret;
+}
+
+/**
+ * Assemble a 64 bit value from two 32 bit values.
+ */
+static jlong jlong_from(jint high, jint low) {
+ jlong result = 0;
+ result = ((jlong)high << 32) | ((0x000000000ffffffff) & (jlong)low);
+ return result;
+}
+
+/*
+ * Fill in the Info object from the OS information about the process.
+ *
+ * Class: java_lang_ProcessHandleImpl
+ * Method: info0
+ * Signature: (J)V
+ */
+JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
+ (JNIEnv *env, jobject jinfo, jlong jpid) {
+ DWORD pid = (DWORD)jpid;
+ int ret = 0;
+ HANDLE handle =
+ OpenProcess(THREAD_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION,
+ FALSE, pid);
+ if (handle == NULL) {
+ return;
+ }
+ getStatInfo(env, handle, jinfo);
+ getCmdlineInfo(env, handle, jinfo);
+ procToUser(env, handle, jinfo);
+
+ CloseHandle(handle); // Ignore return code
+}
+
+/**
+ * Read /proc/<pid>/stat and fill in the fields of the Info object.
+ * The executable name, plus the user, system, and start times are gathered.
+ */
+static void getStatInfo(JNIEnv *env, HANDLE handle, jobject jinfo) {
+ FILETIME CreationTime;
+ FILETIME ExitTime;
+ FILETIME KernelTime;
+ FILETIME UserTime;
+ jlong userTime; // nanoseconds
+ jlong totalTime; // nanoseconds
+ jlong startTime; // nanoseconds
+ UserTime.dwHighDateTime = 0;
+ UserTime.dwLowDateTime = 0;
+ KernelTime.dwHighDateTime = 0;
+ KernelTime.dwLowDateTime = 0;
+ CreationTime.dwHighDateTime = 0;
+ CreationTime.dwLowDateTime = 0;
+
+ if (GetProcessTimes(handle, &CreationTime, &ExitTime, &KernelTime, &UserTime)) {
+ userTime = jlong_from(UserTime.dwHighDateTime, UserTime.dwLowDateTime);
+ totalTime = jlong_from( KernelTime.dwHighDateTime, KernelTime.dwLowDateTime);
+ totalTime = (totalTime + userTime) * 100; // convert sum to nano-seconds
+
+ startTime = jlong_from(CreationTime.dwHighDateTime,
+ CreationTime.dwLowDateTime) / 10000;
+ startTime -= 11644473600000L; // Rebase Epoch from 1601 to 1970
+
+ (*env)->SetLongField(env, jinfo,
+ ProcessHandleImpl_Info_totalTimeID, totalTime);
+ JNU_CHECK_EXCEPTION(env);
+ (*env)->SetLongField(env, jinfo,
+ ProcessHandleImpl_Info_startTimeID, startTime);
+ JNU_CHECK_EXCEPTION(env);
+ }
+}
+
+static void getCmdlineInfo(JNIEnv *env, HANDLE handle, jobject jinfo) {
+ char exeName[1024];
+ int bufsize = sizeof exeName;
+ jstring commandObj;
+
+ if (QueryFullProcessImageName(handle, 0, exeName, &bufsize)) {
+ commandObj = (*env)->NewStringUTF(env, exeName);
+ CHECK_NULL(commandObj);
+ (*env)->SetObjectField(env, jinfo,
+ ProcessHandleImpl_Info_commandID, commandObj);
+ }
+}
+
+static void procToUser( JNIEnv *env, HANDLE handle, jobject jinfo) {
+#define TOKEN_LEN 256
+ DWORD token_len = TOKEN_LEN;
+ char token_buf[TOKEN_LEN];
+ TOKEN_USER *token_user = (TOKEN_USER*)token_buf;
+ HANDLE tokenHandle;
+ WCHAR domain[255];
+ WCHAR name[255];
+ DWORD domainLen = sizeof(domain);
+ DWORD nameLen = sizeof(name);
+ SID_NAME_USE use;
+ jstring s;
+ int ret;
+
+ if (!OpenProcessToken(handle, TOKEN_READ, &tokenHandle)) {
+ return;
+ }
+
+ ret = GetTokenInformation(tokenHandle, TokenUser, token_user,
+ token_len, &token_len);
+ CloseHandle(tokenHandle); // always close handle
+ if (!ret) {
+ JNU_ThrowByNameWithLastError(env,
+ "java/lang/RuntimeException", "GetTokenInformation");
+ return;
+ }
+
+ if (LookupAccountSidW(NULL, token_user->User.Sid, &name[0], &nameLen,
+ &domain[0], &domainLen, &use) == 0) {
+ // Name not available
+ return;
+ }
+
+ s = (*env)->NewString(env, (const jchar *)name, (jsize)wcslen(name));
+ CHECK_NULL(s);
+ (*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, s);
+}
--- a/jdk/test/TEST.ROOT Fri May 29 10:28:28 2015 -0700
+++ b/jdk/test/TEST.ROOT Fri May 29 14:04:12 2015 -0400
@@ -12,7 +12,7 @@
keys=2d dnd i18n intermittent randomness
# Tests that must run in othervm mode
-othervm.dirs=java/awt java/beans javax/accessibility javax/imageio javax/sound javax/print javax/management com/sun/awt sun/awt sun/java2d sun/pisces javax/xml/jaxp/testng/validation
+othervm.dirs=java/awt java/beans javax/accessibility javax/imageio javax/sound javax/print javax/management com/sun/awt sun/awt sun/java2d sun/pisces javax/xml/jaxp/testng/validation java/lang/ProcessHandle
# Tests that cannot run concurrently
exclusiveAccess.dirs=java/rmi/Naming java/util/prefs sun/management/jmxremote sun/tools/jstatd sun/security/mscapi java/util/stream javax/rmi
--- a/jdk/test/java/lang/ProcessBuilder/Basic.java Fri May 29 10:28:28 2015 -0700
+++ b/jdk/test/java/lang/ProcessBuilder/Basic.java Fri May 29 14:04:12 2015 -0400
@@ -1175,13 +1175,13 @@
equal(actualPid, expectedPid);
// Test the default implementation of Process.getPid
- try {
- DelegatingProcess p = new DelegatingProcess(null);
- p.getPid();
- fail("non-overridden Process.getPid method should throw UOE");
- } catch (UnsupportedOperationException uoe) {
- // correct
- }
+ DelegatingProcess p = new DelegatingProcess(null);
+ THROWS(UnsupportedOperationException.class,
+ () -> p.getPid(),
+ () -> p.toHandle(),
+ () -> p.supportsNormalTermination(),
+ () -> p.children(),
+ () -> p.allChildren());
}
@@ -2604,7 +2604,7 @@
static volatile int passed = 0, failed = 0;
static void pass() {passed++;}
static void fail() {failed++; Thread.dumpStack();}
- static void fail(String msg) {System.out.println(msg); fail();}
+ static void fail(String msg) {System.err.println(msg); fail();}
static void unexpected(Throwable t) {failed++; t.printStackTrace();}
static void check(boolean cond) {if (cond) pass(); else fail();}
static void check(boolean cond, String m) {if (cond) pass(); else fail(m);}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/Basic.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2014, 2015, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+
+import java.io.IOException;
+import java.util.Optional;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @summary Basic tests for ProcessHandler
+ * @author Roger Riggs
+ */
+public class Basic {
+ /**
+ * Tests of ProcessHandle.current.
+ */
+ @Test
+ public static void test1() {
+ try {
+ ProcessHandle self = ProcessHandle.current();
+ ProcessHandle self1 = ProcessHandle.current();
+ assertEquals(self, self1); //, "get pid twice should be same %d: %d");
+ } finally {
+ // Cleanup any left over processes
+ ProcessHandle.current().children().forEach(ProcessHandle::destroy);
+ }
+ }
+
+ /**
+ * Tests of ProcessHandle.get.
+ */
+ @Test
+ public static void test2() {
+ try {
+ ProcessHandle self = ProcessHandle.current();
+ long pid = self.getPid(); // known native process id
+ Optional<ProcessHandle> self1 = ProcessHandle.of(pid);
+ assertEquals(self1.get(), self,
+ "ProcessHandle.of(x.getPid()) should be equal getPid() %d: %d");
+
+ Optional<ProcessHandle> ph = ProcessHandle.of(pid);
+ assertEquals(pid, ph.get().getPid());
+ } finally {
+ // Cleanup any left over processes
+ ProcessHandle.current().children().forEach(ProcessHandle::destroy);
+ }
+ }
+
+ @Test
+ public static void test3() {
+ // Test can get parent of current
+ ProcessHandle ph = ProcessHandle.current();
+ try {
+ Optional<ProcessHandle> pph = ph.parent();
+ assertTrue(pph.isPresent(), "Current has a Parent");
+ } finally {
+ // Cleanup any left over processes
+ ProcessHandle.current().children().forEach(ProcessHandle::destroy);
+ }
+ }
+
+ @Test
+ public static void test4() {
+ try {
+ Process p = new ProcessBuilder("sleep", "0").start();
+ p.waitFor();
+
+ long deadPid = p.getPid();
+ p = null; // Forget the process
+
+ Optional<ProcessHandle> t = ProcessHandle.of(deadPid);
+ assertFalse(t.isPresent(), "Handle created for invalid pid:" + t);
+ } catch (IOException | InterruptedException ex) {
+ fail("Unexpected exception", ex);
+ } finally {
+ // Cleanup any left over processes
+ ProcessHandle.current().children().forEach(ProcessHandle::destroy);
+ }
+ }
+
+ @Test
+ public static void test5() {
+ // Always contains itself.
+ ProcessHandle current = ProcessHandle.current();
+ List<ProcessHandle> list = ProcessHandle.allProcesses().collect(Collectors.toList());
+ if (!list.stream()
+ .anyMatch(ph -> ph.equals(ProcessHandle.current()))) {
+ System.out.printf("current: %s%n", current);
+ System.out.printf("all processes.size: %d%n", list.size());
+ list.forEach(p -> ProcessUtil.printProcess(p, " allProcesses: "));
+ fail("current process not found in all processes");
+ }
+ }
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public static void test6() {
+ ProcessHandle.current().onExit();
+ }
+
+ @Test(expectedExceptions = IllegalStateException.class)
+ public static void test7() {
+ ProcessHandle.current().destroyForcibly();
+ }
+
+ // Main can be used to run the tests from the command line with only testng.jar.
+ @SuppressWarnings("raw_types")
+ public static void main(String[] args) {
+ Class<?>[] testclass = {TreeTest.class};
+ TestNG testng = new TestNG();
+ testng.setTestClasses(testclass);
+ testng.run();
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/InfoTest.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,350 @@
+/*
+ * Copyright (c) 2014, 2015, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.lang.ProcessBuilder;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Random;
+import java.util.Scanner;
+import java.util.StringTokenizer;
+import java.util.concurrent.TimeUnit;
+
+import jdk.testlibrary.Platform;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+import org.testng.TestNG;
+
+/*
+ * @test
+ * @library /lib/testlibrary
+ * @summary Functions of ProcessHandle.Info
+ * @author Roger Riggs
+ */
+
+public class InfoTest {
+
+ static String whoami;
+
+ static {
+ ProcessBuilder pb = new ProcessBuilder("whoami");
+ String fullName;
+ try {
+ fullName = new Scanner(pb.start().getInputStream()).nextLine();
+ StringTokenizer st = new StringTokenizer(fullName, "\\");
+ while (st.hasMoreTokens()) {
+ whoami = st.nextToken();
+ }
+ System.out.printf("whoami: %s, user.name: %s%n", whoami, System.getProperty("user.name"));
+ } catch (IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // Main can be used to run the tests from the command line with only testng.jar.
+ @SuppressWarnings("raw_types")
+ public static void main(String[] args) {
+ Class<?>[] testclass = {InfoTest.class};
+ TestNG testng = new TestNG();
+ testng.setTestClasses(testclass);
+ testng.run();
+ }
+
+ /**
+ * Test that cputime used shows up in ProcessHandle.info
+ */
+ @Test
+ public static void test1() {
+ System.out.println("Note: when run in samevm mode the cputime of the " +
+ "test runner is included.");
+ ProcessHandle self = ProcessHandle.current();
+
+ Duration somecpu = Duration.ofMillis(200L);
+ Instant end = Instant.now().plus(somecpu);
+ while (Instant.now().isBefore(end)) {
+ // waste the cpu
+ }
+ ProcessHandle.Info info = self.info();
+ System.out.printf(" info: %s%n", info);
+ Optional<Duration> totalCpu = info.totalCpuDuration();
+ if (totalCpu.isPresent() && (totalCpu.get().compareTo(somecpu) < 0)) {
+ Assert.fail("reported cputime less than expected: " + somecpu + ", " +
+ "actual: " + info.totalCpuDuration());
+ }
+ }
+
+ /**
+ * Spawn a child with arguments and check they are visible via the ProcessHandle.
+ */
+ @Test
+ public static void test2() {
+ try {
+ long cpulooptime = 1 << 8;
+ String[] extraArgs = {"pid", "parent", "stdin"};
+ Instant beforeStart = Instant.now().truncatedTo(ChronoUnit.SECONDS);
+ JavaChild p1 = JavaChild.spawnJavaChild((Object[])extraArgs);
+ Instant afterStart = Instant.now();
+
+ try (BufferedReader lines = p1.outputReader()) {
+ Duration lastCpu = Duration.ofMillis(0L);
+ for (int j = 0; j < 20; j++) {
+
+ p1.sendAction("cpuloop", cpulooptime);
+ p1.sendAction("cputime", "");
+
+ // Read cputime from child
+ Duration childCpuTime = null;
+ // Read lines from the child until the result from cputime is returned
+ String s;
+ while ((s = lines.readLine()) != null) {
+ String[] split = s.trim().split(" ");
+ if (split.length == 3 && split[1].equals("cputime")) {
+ long nanos = Long.valueOf(split[2]);
+ childCpuTime = Duration.ofNanos(nanos);
+ break; // found the result we're looking for
+ }
+ }
+
+
+ ProcessHandle.Info info = p1.info();
+ System.out.printf(" info: %s%n", info);
+
+ if (info.user().isPresent()) {
+ String user = info.user().get();
+ Assert.assertNotNull(user, "User name");
+ Assert.assertEquals(user, whoami, "User name");
+ }
+
+ Optional<String> command = info.command();
+ if (command.isPresent()) {
+ String javaExe = System.getProperty("test.jdk") +
+ File.separator + "bin" + File.separator + "java";
+ String expected = Platform.isWindows() ? javaExe + ".exe" : javaExe;
+ Assert.assertEquals(command.get(), expected,
+ "Command: expected: 'java'" + ", actual: " + command);
+ }
+
+ if (info.arguments().isPresent()) {
+ String[] args = info.arguments().get();
+
+ if (Platform.isLinux() || Platform.isOSX()) {
+ int offset = args.length - extraArgs.length;
+ for (int i = 0; i < extraArgs.length; i++) {
+ Assert.assertEquals(args[offset + i], extraArgs[i],
+ "Actual argument mismatch, index: " + i);
+ }
+ } else if (Platform.isSolaris()) {
+ Assert.assertEquals(args.length, 1,
+ "Expected argument list length: 1");
+ Assert.assertNotNull(args[0],
+ "Expected an argument");
+ } else {
+ System.out.printf("No argument test for OS: %s%n", Platform.getOsName());
+ }
+
+ // Now check that the first argument is not the same as the executed command
+ if (args.length > 0) {
+ Assert.assertNotEquals(args[0], command,
+ "First argument should not be the executable: args[0]: "
+ + args[0] + ", command: " + command);
+ }
+ }
+
+ if (info.totalCpuDuration().isPresent()) {
+ Duration totalCPU = info.totalCpuDuration().get();
+ Duration epsilon = Duration.ofMillis(200L);
+ Assert.assertTrue(totalCPU.toNanos() > 0L,
+ "total cpu time expected > 0ms, actual: " + totalCPU);
+ Assert.assertTrue(totalCPU.toNanos() < lastCpu.toNanos() + 10_000_000_000L,
+ "total cpu time expected < 10s more than previous iteration, actual: " + totalCPU);
+ if (childCpuTime != null) {
+ System.out.printf(" info.totalCPU: %s, childCpuTime: %s, diff: %s%n",
+ totalCPU.toNanos(), childCpuTime.toNanos(), childCpuTime.toNanos() - totalCPU.toNanos());
+ Assert.assertTrue(checkEpsilon(childCpuTime, totalCPU, epsilon),
+ childCpuTime + " should be within " +
+ epsilon + " of " + totalCPU);
+ }
+ lastCpu = totalCPU;
+ }
+
+ if (info.startInstant().isPresent()) {
+ Instant startTime = info.startInstant().get();
+ Assert.assertTrue(startTime.isBefore(afterStart),
+ "startTime after process spawn completed"
+ + startTime + " + > " + afterStart);
+ }
+ }
+ }
+ p1.waitFor(5, TimeUnit.SECONDS);
+ } catch (IOException | InterruptedException ie) {
+ ie.printStackTrace(System.out);
+ Assert.fail("unexpected exception", ie);
+ }
+ }
+
+ /**
+ * Spawn a child with arguments and check they are visible via the ProcessHandle.
+ */
+ @Test
+ public static void test3() {
+ try {
+ for (int sleepTime : Arrays.asList(1, 2)) {
+ Process p = spawn("sleep", String.valueOf(sleepTime));
+ ProcessHandle.Info info = p.info();
+ System.out.printf(" info: %s%n", info);
+
+ if (info.user().isPresent()) {
+ String user = info.user().get();
+ Assert.assertNotNull(user);
+ Assert.assertEquals(user, whoami);
+ }
+ if (info.command().isPresent()) {
+ String command = info.command().get();
+ String expected = Platform.isWindows() ? "sleep.exe" : "sleep";
+ Assert.assertTrue(command.endsWith(expected), "Command: expected: \'" +
+ expected + "\', actual: " + command);
+
+ // Verify the command exists and is executable
+ File exe = new File(command);
+ Assert.assertTrue(exe.exists(), "command must exist: " + exe);
+ Assert.assertTrue(exe.canExecute(), "command must be executable: " + exe);
+ }
+ if (info.arguments().isPresent()) {
+ String[] args = info.arguments().get();
+ if (args.length > 0) {
+ Assert.assertEquals(args[0], String.valueOf(sleepTime));
+ }
+ }
+ Assert.assertTrue(p.waitFor(15, TimeUnit.SECONDS));
+ }
+ } catch (IOException | InterruptedException ex) {
+ ex.printStackTrace(System.out);;
+ } finally {
+ // Destroy any children that still exist
+ ProcessUtil.destroyProcessTree(ProcessHandle.current());
+ }
+ }
+
+ /**
+ * Cross check the cputime reported from java.management with that for the current process.
+ */
+ @Test
+ public static void test4() {
+ Duration myCputime1 = ProcessUtil.MXBeanCpuTime();
+
+ Optional<Duration> dur1 = ProcessHandle.current().info().totalCpuDuration();
+
+ Duration myCputime2 = ProcessUtil.MXBeanCpuTime();
+
+ Optional<Duration> dur2 = ProcessHandle.current().info().totalCpuDuration();
+
+ if (dur1.isPresent() && dur2.isPresent()) {
+ Duration total1 = dur1.get();
+ Duration total2 = dur2.get(); ;
+ System.out.printf(" total1 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n",
+ Objects.toString(total1), myCputime1, myCputime1.minus(total1));
+ System.out.printf(" total2 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n",
+ Objects.toString(total2), myCputime2, myCputime2.minus(total2));
+
+ Duration epsilon = Duration.ofMillis(200L); // Epsilon is 200ms.
+ Assert.assertTrue(checkEpsilon(myCputime1, myCputime2, epsilon),
+ myCputime1.toNanos() + " should be within " + epsilon
+ + " of " + myCputime2.toNanos());
+ Assert.assertTrue(checkEpsilon(total1, total2, epsilon),
+ total1.toNanos() + " should be within " + epsilon
+ + " of " + total2.toNanos());
+ Assert.assertTrue(checkEpsilon(myCputime1, total1, epsilon),
+ myCputime1.toNanos() + " should be within " + epsilon
+ + " of " + total1.toNanos());
+ Assert.assertTrue(checkEpsilon(total1, myCputime2, epsilon),
+ total1.toNanos() + " should be within " + epsilon
+ + " of " + myCputime2.toNanos());
+ Assert.assertTrue(checkEpsilon(myCputime2, total2, epsilon),
+ myCputime2.toNanos() + " should be within " + epsilon
+ + " of " + total2.toNanos());
+ }
+ }
+
+ @Test
+ public static void test5() {
+ ProcessHandle self = ProcessHandle.current();
+ Random r = new Random();
+ for (int i = 0; i < 30; i++) {
+ Instant end = Instant.now().plusMillis(500L);
+ while (end.isBefore(Instant.now())) {
+ // burn the cpu time checking the time
+ long x = r.nextLong();
+ }
+ if (self.info().totalCpuDuration().isPresent()) {
+ Duration totalCpu = self.info().totalCpuDuration().get();
+ long infoTotalCputime = totalCpu.toNanos();
+ long beanCputime = ProcessUtil.MXBeanCpuTime().toNanos();
+ System.out.printf(" infoTotal: %12d, beanCpu: %12d, diff: %12d%n",
+ infoTotalCputime, beanCputime, beanCputime - infoTotalCputime);
+ } else {
+ break; // nothing to compare; continue
+ }
+ }
+ }
+ /**
+ * Check two Durations, the second should be greater than the first or
+ * within the supplied Epsilon.
+ * @param d1 a Duration - presumed to be shorter
+ * @param d2 a 2nd Duration - presumed to be greater (or within Epsilon)
+ * @param epsilon Epsilon the amount of overlap allowed
+ * @return
+ */
+ static boolean checkEpsilon(Duration d1, Duration d2, Duration epsilon) {
+ if (d1.toNanos() <= d2.toNanos()) {
+ return true;
+ }
+ Duration diff = d1.minus(d2).abs();
+ return diff.compareTo(epsilon) <= 0;
+ }
+
+ /**
+ * Spawn a native process with the provided arguments.
+ * @param command the executable of native process
+ * @args
+ * @return the Process that was started
+ * @throws IOException thrown by ProcessBuilder.start
+ */
+ static Process spawn(String command, String... args) throws IOException {
+ ProcessBuilder pb = new ProcessBuilder();
+ pb.inheritIO();
+ List<String> list = new ArrayList<>();
+ list.add(command);
+ for (String arg : args)
+ list.add(arg);
+ pb.command(list);
+ return pb.start();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/JavaChild.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,524 @@
+/*
+ * Copyright (c) 2014, 2015, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import com.sun.management.OperatingSystemMXBean;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.Reader;
+import java.io.PrintWriter;
+import java.lang.InterruptedException;
+import java.lang.Override;
+import java.lang.management.ManagementFactory;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
+
+
+/**
+ * Command driven subprocess with useful child functions.
+ */
+public class JavaChild extends Process {
+
+private static volatile int commandSeq = 0; // Command sequence number
+ private static final ProcessHandle self = ProcessHandle.current();
+ private static int finalStatus = 0;
+ private static final List<JavaChild> children = new ArrayList<>();
+ private static final Set<JavaChild> completedChildren =
+ Collections.synchronizedSet(new HashSet<>());
+
+ private final Process delegate;
+ private final PrintWriter inputWriter;
+ private final BufferedReader outputReader;
+
+
+ /**
+ * Create a JavaChild control instance that delegates to the spawned process.
+ * {@link #sendAction} is used to send commands via the processes stdin.
+ * {@link #forEachOutputLine} can be used to process output from the child
+ * @param delegate the process to delegate and send commands to and get responses from
+ */
+ private JavaChild(Process delegate) {
+ this.delegate = delegate;
+ // Initialize PrintWriter with autoflush (on println)
+ inputWriter = new PrintWriter(delegate.getOutputStream(), true);
+ outputReader = new BufferedReader(new InputStreamReader(delegate.getInputStream()));
+ }
+
+ @Override
+ public void destroy() {
+ delegate.destroy();
+ }
+
+ @Override
+ public int exitValue() {
+ return delegate.exitValue();
+ }
+
+ @Override
+ public int waitFor() throws InterruptedException {
+ return delegate.waitFor();
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return delegate.getOutputStream();
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return delegate.getInputStream();
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return delegate.getErrorStream();
+ }
+
+ @Override
+ public ProcessHandle toHandle() {
+ return delegate.toHandle();
+ }
+
+ @Override
+ public CompletableFuture<Process> onExit() {
+ return delegate.onExit();
+ }
+ @Override
+ public String toString() {
+ return "delegate: " + delegate.toString();
+ }
+
+ public CompletableFuture<JavaChild> onJavaChildExit() {
+ return onExit().thenApply(ph -> this);
+ }
+
+ /**
+ * Send an action and arguments to the child via stdin.
+ * @param action the action
+ * @param args additional arguments
+ * @throws IOException if something goes wrong writing to the child
+ */
+ void sendAction(String action, Object... args) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ sb.append(action);
+ for (Object arg :args) {
+ sb.append(" ");
+ sb.append(arg);
+ }
+ String cmd = sb.toString();
+ synchronized (this) {
+ inputWriter.println(cmd);
+ }
+ }
+
+ public BufferedReader outputReader() {
+ return outputReader;
+ }
+
+ /**
+ * Asynchronously evaluate each line of output received back from the child process.
+ * @param consumer a Consumer of each line read from the child
+ * @return a CompletableFuture that is completed when the child closes System.out.
+ */
+ CompletableFuture<String> forEachOutputLine(Consumer<String> consumer) {
+ final CompletableFuture<String> future = new CompletableFuture<>();
+ String name = "OutputLineReader-" + getPid();
+ Thread t = new Thread(() -> {
+ try (BufferedReader reader = outputReader()) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ consumer.accept(line);
+ }
+ } catch (IOException | RuntimeException ex) {
+ consumer.accept("IOE (" + getPid() + "):" + ex.getMessage());
+ future.completeExceptionally(ex);
+ }
+ future.complete("success");
+ }, name);
+ t.start();
+ return future;
+ }
+
+ /**
+ * Spawn a JavaChild with the provided arguments.
+ * Commands can be send to the child with {@link #sendAction}.
+ * Output lines from the child can be processed with {@link #forEachOutputLine}.
+ * System.err is set to inherit and is the unstructured async logging
+ * output for all subprocesses.
+ * @param args the command line arguments to JavaChild
+ * @return the JavaChild that was started
+ * @throws IOException thrown by ProcessBuilder.start
+ */
+ static JavaChild spawnJavaChild(Object... args) throws IOException {
+ String[] stringArgs = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ stringArgs[i] = args[i].toString();
+ }
+ ProcessBuilder pb = build(stringArgs);
+ pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+ return new JavaChild(pb.start());
+ }
+
+ /**
+ * Spawn a JavaChild with the provided arguments.
+ * Sets the process to inherit the I/O channels.
+ * @param args the command line arguments to JavaChild
+ * @return the Process that was started
+ * @throws IOException thrown by ProcessBuilder.start
+ */
+ static Process spawn(String... args) throws IOException {
+ ProcessBuilder pb = build(args);
+ pb.inheritIO();
+ return pb.start();
+ }
+
+ /**
+ * Return a ProcessBuilder with the javaChildArgs and
+ * any additional supplied args.
+ *
+ * @param args the command line arguments to JavaChild
+ * @return the ProcessBuilder
+ */
+ static ProcessBuilder build(String ... args) {
+ ProcessBuilder pb = new ProcessBuilder();
+ List<String> list = new ArrayList<>(javaChildArgs);
+ for (String arg : args)
+ list.add(arg);
+ pb.command(list);
+ return pb;
+ }
+
+ static final String javaHome = (System.getProperty("test.jdk") != null)
+ ? System.getProperty("test.jdk")
+ : System.getProperty("java.home");
+
+ static final String javaExe =
+ javaHome + File.separator + "bin" + File.separator + "java";
+
+ static final String classpath =
+ System.getProperty("java.class.path");
+
+ static final List<String> javaChildArgs =
+ Arrays.asList(javaExe,
+ "-XX:+DisplayVMOutputToStderr",
+ "-Dtest.jdk=" + javaHome,
+ "-classpath", absolutifyPath(classpath),
+ "JavaChild");
+
+ private static String absolutifyPath(String path) {
+ StringBuilder sb = new StringBuilder();
+ for (String file : path.split(File.pathSeparator)) {
+ if (sb.length() != 0)
+ sb.append(File.pathSeparator);
+ sb.append(new File(file).getAbsolutePath());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Main program that interprets commands from the command line args or stdin.
+ * Each command produces output to stdout confirming the command and
+ * providing results.
+ * System.err is used for unstructured information.
+ * @param args an array of strings to be interpreted as commands;
+ * each command uses additional arguments as needed
+ */
+ public static void main(String[] args) {
+ System.out.printf("args: %s %s%n", ProcessHandle.current(), Arrays.toString(args));
+ interpretCommands(args);
+ System.exit(finalStatus);
+ }
+
+ /**
+ * Interpret an array of strings as a command line.
+ * @param args an array of strings to be interpreted as commands;
+ * each command uses additional arguments as needed
+ */
+ private static void interpretCommands(String[] args) {
+ try {
+ int nextArg = 0;
+ while (nextArg < args.length) {
+ String action = args[nextArg++];
+ switch (action) {
+ case "help":
+ sendResult(action, "");
+ help();
+ break;
+ case "sleep":
+ int millis = Integer.valueOf(args[nextArg++]);
+ Thread.sleep(millis);
+ sendResult(action, Integer.toString(millis));
+ break;
+ case "cpuloop":
+ long times = Long.valueOf(args[nextArg++]);
+ Instant end = Instant.now().plusMillis(times);
+ while (Instant.now().isBefore(end)) {
+ // burn the cpu til the time is up
+ }
+ sendResult(action, times);
+ break;
+ case "cputime":
+ sendResult(action, getCpuTime());
+ break;
+ case "out":
+ case "err":
+ String value = args[nextArg++];
+ sendResult(action, value);
+ if (action.equals("err")) {
+ System.err.println(value);
+ }
+ break;
+ case "stdin":
+ // Read commands from stdin; at eof, close stdin of
+ // children and wait for each to exit
+ sendResult(action, "start");
+ try (Reader reader = new InputStreamReader(System.in);
+ BufferedReader input = new BufferedReader(reader)) {
+ String line;
+ while ((line = input.readLine()) != null) {
+ line = line.trim();
+ if (!line.isEmpty()) {
+ String[] split = line.split("\\s");
+ interpretCommands(split);
+ }
+ }
+ // EOF on stdin, close stdin on all spawned processes
+ for (JavaChild p : children) {
+ try {
+ p.getOutputStream().close();
+ } catch (IOException ie) {
+ sendResult("stdin_closing", p.getPid(),
+ "exception", ie.getMessage());
+ }
+ }
+
+ for (JavaChild p : children) {
+ do {
+ try {
+ p.waitFor();
+ break;
+ } catch (InterruptedException e) {
+ // retry
+ }
+ } while (true);
+ }
+ // Wait for all children to be gone
+ Instant timeOut = Instant.now().plusSeconds(10L);
+ while (!completedChildren.containsAll(children)) {
+ if (Instant.now().isBefore(timeOut)) {
+ Thread.sleep(100L);
+ } else {
+ System.err.printf("Timeout waiting for " +
+ "children to terminate%n");
+ children.removeAll(completedChildren);
+ for (JavaChild c : children) {
+ sendResult("stdin_noterm", c.getPid());
+ System.err.printf(" Process not terminated: " +
+ "pid: %d%n", c.getPid());
+ }
+ System.exit(2);
+ }
+ }
+ }
+ sendResult(action, "done");
+ return; // normal exit from JavaChild Process
+ case "parent":
+ sendResult(action, self.parent().toString());
+ break;
+ case "pid":
+ sendResult(action, self.toString());
+ break;
+ case "exit":
+ int exitValue = (nextArg < args.length)
+ ? Integer.valueOf(args[nextArg]) : 0;
+ sendResult(action, exitValue);
+ System.exit(exitValue);
+ break;
+ case "spawn": {
+ if (args.length - nextArg < 2) {
+ throw new RuntimeException("not enough args for respawn: " +
+ (args.length - 2));
+ }
+ // Spawn as many children as requested and
+ // pass on rest of the arguments
+ int ncount = Integer.valueOf(args[nextArg++]);
+ Object[] subargs = new String[args.length - nextArg];
+ System.arraycopy(args, nextArg, subargs, 0, subargs.length);
+ for (int i = 0; i < ncount; i++) {
+ JavaChild p = spawnJavaChild(subargs);
+ sendResult(action, p.getPid());
+ p.forEachOutputLine(JavaChild::sendRaw);
+ p.onJavaChildExit().thenAccept((p1) -> {
+ int excode = p1.exitValue();
+ sendResult("child_exit", p1.getPid(), excode);
+ completedChildren.add(p1);
+ });
+ children.add(p); // Add child to spawned list
+ }
+ nextArg = args.length;
+ break;
+ }
+ case "child": {
+ // Send the command to all the live children;
+ // ignoring those that are not alive
+ int sentCount = 0;
+ Object[] result =
+ Arrays.copyOfRange(args, nextArg - 1, args.length);
+ Object[] subargs =
+ Arrays.copyOfRange(args, nextArg + 1, args.length);
+ for (JavaChild p : children) {
+ if (p.isAlive()) {
+ sentCount++;
+ // overwrite with current pid
+ result[0] = Long.toString(p.getPid());
+ sendResult(action, result);
+ p.sendAction(args[nextArg], subargs);
+ }
+ }
+ if (sentCount == 0) {
+ sendResult(action, "n/a");
+ }
+ nextArg = args.length;
+ break;
+ }
+ case "child_eof" :
+ // Close the InputStream of all the live children;
+ // ignoring those that are not alive
+ for (JavaChild p : children) {
+ if (p.isAlive()) {
+ sendResult(action, p.getPid());
+ p.getOutputStream().close();
+ }
+ }
+ break;
+ case "property":
+ String name = args[nextArg++];
+ sendResult(action, name, System.getProperty(name));
+ break;
+ case "threaddump":
+ Thread.dumpStack();
+ break;
+ default:
+ throw new Error("JavaChild action unknown: " + action);
+ }
+ }
+ } catch (Throwable t) {
+ t.printStackTrace(System.err);
+ System.exit(1);
+ }
+ }
+
+ static synchronized void sendRaw(String s) {
+ System.out.println(s);
+ System.out.flush();
+ }
+ static void sendResult(String action, Object... results) {
+ sendRaw(new Event(action, results).toString());
+ }
+
+ static long getCpuTime() {
+ OperatingSystemMXBean osMbean =
+ (OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();
+ return osMbean.getProcessCpuTime();
+ }
+
+ /**
+ * Print command usage to stderr.
+ */
+ private static void help() {
+ System.err.println("Commands:");
+ System.err.println(" help");
+ System.err.println(" pid");
+ System.err.println(" parent");
+ System.err.println(" cpuloop <loopcount>");
+ System.err.println(" cputime");
+ System.err.println(" stdin - read commands from stdin");
+ System.err.println(" sleep <millis>");
+ System.err.println(" spawn <n> command... - spawn n new children and send command");
+ System.err.println(" child command... - send command to all live children");
+ System.err.println(" child_eof - send eof to all live children");
+ System.err.println(" exit <exitcode>");
+ System.err.println(" out arg...");
+ System.err.println(" err arg...");
+ }
+
+ static class Event {
+ long pid;
+ long seq;
+ String command;
+ Object[] results;
+ Event(String command, Object... results) {
+ this(self.getPid(), ++commandSeq, command, results);
+ }
+ Event(long pid, int seq, String command, Object... results) {
+ this.pid = pid;
+ this.seq = seq;
+ this.command = command;
+ this.results = results;
+ }
+
+ /**
+ * Create a String encoding the pid, seq, command, and results.
+ *
+ * @return a String formatted to send to the stream.
+ */
+ String format() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(pid);
+ sb.append(":");
+ sb.append(seq);
+ sb.append(" ");
+ sb.append(command);
+ for (int i = 0; i < results.length; i++) {
+ sb.append(" ");
+ sb.append(results[i]);
+ }
+ return sb.toString();
+ }
+
+ Event(String encoded) {
+ String[] split = encoded.split("\\s");
+ String[] pidSeq = split[0].split(":");
+ pid = Long.valueOf(pidSeq[0]);
+ seq = Integer.valueOf(pidSeq[1]);
+ command = split[1];
+ Arrays.copyOfRange(split, 1, split.length);
+ }
+
+ public String toString() {
+ return format();
+ }
+
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/OnExitTest.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2014, 2015, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.lang.InterruptedException;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+import jdk.testlibrary.Platform;
+import org.testng.annotations.Test;
+import org.testng.Assert;
+import org.testng.TestNG;
+
+/*
+ * @test
+ * @library /lib/testlibrary
+ * @summary Functions of Process.onExit and ProcessHandle.onExit
+ * @author Roger Riggs
+ */
+
+public class OnExitTest extends ProcessUtil {
+
+ @SuppressWarnings("raw_types")
+ public static void main(String[] args) {
+ Class<?>[] testclass = { OnExitTest.class};
+ TestNG testng = new TestNG();
+ testng.setTestClasses(testclass);
+ testng.run();
+ }
+
+ /**
+ * Basic test of exitValue and onExit.
+ */
+ @Test
+ public static void test1() {
+ try {
+ int[] exitValues = {0, 1, 10};
+ for (int value : exitValues) {
+ Process p = JavaChild.spawn("exit", Integer.toString(value));
+ CompletableFuture<Process> future = p.onExit();
+ future.thenAccept( (ph) -> {
+ int actualExitValue = ph.exitValue();
+ printf(" javaChild done: %s, exitStatus: %d%n",
+ ph, actualExitValue);
+ Assert.assertEquals(actualExitValue, value, "actualExitValue incorrect");
+ Assert.assertEquals(ph, p, "Different Process passed to thenAccept");
+ });
+
+ Process h = future.get();
+ Assert.assertEquals(h, p);
+ Assert.assertEquals(p.exitValue(), value);
+ Assert.assertFalse(p.isAlive(), "Process should not be alive");
+ p.waitFor();
+ }
+ } catch (IOException | InterruptedException | ExecutionException ex) {
+ Assert.fail(ex.getMessage(), ex);
+ } finally {
+ destroyProcessTree(ProcessHandle.current());
+ }
+ }
+
+ /**
+ * Test of Completion handler when parent is killed.
+ * Spawn 1 child to spawn 3 children each with 2 children.
+ */
+ @Test
+ public static void test2() {
+ try {
+ ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();
+ List<ProcessHandle> children = getChildren(ProcessHandle.current());
+ children.forEach(ProcessUtil::printProcess);
+ Assert.assertEquals(children.size(), 0,
+ "Expected to start with zero children; " + children);
+
+ JavaChild proc = JavaChild.spawnJavaChild("stdin");
+ ProcessHandle procHandle = proc.toHandle();
+ printf(" spawned: %d%n", proc.getPid());
+
+ proc.forEachOutputLine((s) -> {
+ String[] split = s.trim().split(" ");
+ if (split.length == 3 && split[1].equals("spawn")) {
+ Long child = Long.valueOf(split[2]);
+ Long parent = Long.valueOf(split[0].split(":")[0]);
+ processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get());
+ }
+ });
+
+ proc.sendAction("spawn", "3", "stdin");
+
+ proc.sendAction("child", "spawn", "2", "stdin");
+
+ // Poll until all 9 child processes exist or the timeout is reached
+ int expected = 9;
+ Instant endTimeout = Instant.now().plusSeconds(10L);
+ do {
+ Thread.sleep(200L);
+ printf(" subprocess count: %d, waiting for %d%n", processes.size(), expected);
+ } while (processes.size() < expected &&
+ Instant.now().isBefore(endTimeout));
+
+ children = getAllChildren(procHandle);
+
+ ArrayBlockingQueue<ProcessHandle> completions = new ArrayBlockingQueue<>(expected + 1);
+ Instant startTime = Instant.now();
+ // Create a future for each of the 9 children
+ processes.forEach( (p, parent) -> {
+ p.onExit().whenComplete((ph, ex) -> {
+ Duration elapsed = Duration.between(startTime, Instant.now());
+ completions.add(ph);
+ printf("whenComplete: pid: %s, exception: %s, thread: %s, elapsed: %s%n",
+ ph, ex, Thread.currentThread(), elapsed);
+ });
+ });
+
+ // Check that each of the spawned processes is included in the children
+ List<ProcessHandle> remaining = new ArrayList<>(children);
+ processes.forEach((p, parent) -> {
+ Assert.assertTrue(remaining.remove(p), "spawned process should have been in children");
+ });
+
+ // Remove Win32 system spawned conhost.exe processes
+ remaining.removeIf(ProcessUtil::isWindowsConsole);
+
+ remaining.forEach(p -> printProcess(p, "unexpected: "));
+ if (remaining.size() > 0) {
+ // Show full list for debugging
+ ProcessUtil.logTaskList();
+ }
+
+ proc.destroy(); // kill off the parent
+ proc.waitFor();
+
+ // Wait for all the processes to be completed
+ processes.forEach((p, parent) -> {
+ try {
+ p.onExit().get();
+ } catch (InterruptedException | ExecutionException ex) {
+ // ignore
+ }
+ });
+
+ // Verify that all 9 exit handlers were called
+ processes.forEach((p, parent) ->
+ Assert.assertTrue(completions.contains(p), "Child onExit not called: " + p
+ + ", parent: " + parent
+ + ": " + p.info()));
+
+ // Show the status of the original children
+ children.forEach(p -> printProcess(p, "after onExit:"));
+
+ Assert.assertEquals(proc.isAlive(), false, "destroyed process is alive:: %s%n" + proc);
+
+ List<ProcessHandle> children2 = getAllChildren(procHandle);
+ printf(" children2: %s%n", children2.toString());
+ Assert.assertEquals(children2.size(), 0, "After onExit, expected no children");
+
+ Assert.assertEquals(remaining.size(), 0, "Unaccounted for children");
+
+ } catch (IOException | InterruptedException ex) {
+ Assert.fail(ex.getMessage());
+ } finally {
+ destroyProcessTree(ProcessHandle.current());
+ }
+ }
+
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/PermissionTest.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,222 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.FilePermission;
+import java.io.IOException;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.security.SecurityPermission;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.PropertyPermission;
+
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeGroups;
+import org.testng.annotations.Test;
+
+public class PermissionTest {
+ /**
+ * Backing up policy.
+ */
+ protected static Policy policy;
+
+ /**
+ * Backing up security manager.
+ */
+ private static SecurityManager sm;
+
+ /**
+ * Current process handle.
+ */
+ private final ProcessHandle currentHndl;
+
+ PermissionTest() {
+ policy = Policy.getPolicy();
+ sm = System.getSecurityManager();
+ currentHndl = ProcessHandle.current();
+ }
+
+ @Test
+ public void allChildrenWithPermission() {
+ Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
+ currentHndl.allChildren();
+ }
+
+ @Test
+ public void allProcessesWithPermission() {
+ Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
+ ProcessHandle.allProcesses();
+ }
+
+ @Test
+ public void childrenWithPermission() {
+ Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
+ currentHndl.children();
+ }
+
+ @Test
+ public void currentWithPermission() {
+ Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
+ ProcessHandle.current();
+ }
+
+ @Test
+ public void ofWithPermission() {
+ Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
+ ProcessHandle.of(0);
+ }
+
+ @Test
+ public void parentWithPermission() {
+ Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
+ currentHndl.parent();
+ }
+
+ @Test
+ public void processToHandleWithPermission() throws IOException {
+ Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
+ Process p = null;
+ try {
+ ProcessBuilder pb = new ProcessBuilder("sleep", "30");
+ p = pb.start();
+ ProcessHandle ph = p.toHandle();
+ Assert.assertNotNull(ph, "ProcessHandle expected from Process");
+ } finally {
+ if (p != null) {
+ p.destroy();
+ }
+ }
+ }
+
+ @BeforeGroups (groups = {"NoManageProcessPermission"})
+ public void noPermissionsSetup(){
+ Policy.setPolicy(new TestPolicy());
+ SecurityManager sm = new SecurityManager();
+ System.setSecurityManager(sm);
+ }
+
+ @Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
+ public void noPermissionAllChildren() {
+ currentHndl.allChildren();
+ }
+
+ @Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
+ public void noPermissionAllProcesses() {
+ ProcessHandle.allProcesses();
+ }
+
+ @Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
+ public void noPermissionChildren() {
+ currentHndl.children();
+ }
+
+ @Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
+ public void noPermissionCurrent() {
+ ProcessHandle.current();
+ }
+
+ @Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
+ public void noPermissionOf() {
+ ProcessHandle.of(0);
+ }
+
+ @Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
+ public void noPermissionParent() {
+ currentHndl.parent();
+ }
+
+ @Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
+ public void noPermissionProcessToHandle() throws IOException {
+ Process p = null;
+ try {
+ ProcessBuilder pb = new ProcessBuilder("sleep", "30");
+ p = pb.start();
+ ProcessHandle ph = p.toHandle();
+ Assert.assertNotNull(ph, "ProcessHandle expected from Process");
+ } finally {
+ if (p != null) {
+ p.destroy();
+ }
+ }
+ }
+
+ @AfterClass
+ public void tearDownClass() throws Exception {
+ System.setSecurityManager(sm);
+ Policy.setPolicy(policy);
+ }
+}
+
+class TestPolicy extends Policy {
+ private final PermissionCollection permissions = new Permissions();
+
+ public TestPolicy() {
+ setBasicPermissions();
+ }
+
+ /*
+ * Defines the minimal permissions required by testNG and set security
+ * manager permission when running these tests.
+ */
+ public void setBasicPermissions() {
+ permissions.add(new SecurityPermission("getPolicy"));
+ permissions.add(new SecurityPermission("setPolicy"));
+ permissions.add(new RuntimePermission("getClassLoader"));
+ permissions.add(new RuntimePermission("setSecurityManager"));
+ permissions.add(new RuntimePermission("createSecurityManager"));
+ permissions.add(new PropertyPermission("testng.show.stack.frames",
+ "read"));
+ permissions.add(new PropertyPermission("user.dir", "read"));
+ permissions.add(new PropertyPermission("test.src", "read"));
+ permissions.add(new PropertyPermission("file.separator", "read"));
+ permissions.add(new PropertyPermission("line.separator", "read"));
+ permissions.add(new PropertyPermission("fileStringBuffer", "read"));
+ permissions.add(new PropertyPermission("dataproviderthreadcount", "read"));
+ permissions.add(new FilePermission("<<ALL FILES>>", "execute"));
+ }
+
+ public TestPolicy(Permission... ps) {
+ setBasicPermissions();
+ Arrays.stream(ps).forEach(p -> permissions.add(p));
+ }
+
+ @Override
+ public PermissionCollection getPermissions(ProtectionDomain domain) {
+ return permissions;
+ }
+
+ @Override
+ public PermissionCollection getPermissions(CodeSource codesource) {
+ return permissions;
+ }
+
+ @Override
+ public boolean implies(ProtectionDomain domain, Permission perm) {
+ return permissions.implies(perm);
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/ProcessUtil.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,246 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.lang.management.ManagementFactory;
+import java.lang.ProcessBuilder;
+import java.time.Duration;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import com.sun.management.OperatingSystemMXBean;
+
+import jdk.testlibrary.Platform;
+
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * Useful utilities for testing Process and ProcessHandle.
+ */
+public abstract class ProcessUtil {
+ /**
+ * Constructor
+ */
+ public ProcessUtil() {}
+
+ /**
+ * Returns the list of direct children.
+ * WIndows conhost.exe children are filtered out.
+ * @param ph the Process to get children of
+ * @return a list of child ProcessHandles
+ */
+ public static List<ProcessHandle> getChildren(ProcessHandle ph) {
+ return ph.children()
+ .filter(ProcessUtil::isNotWindowsConsole)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Returns the list of all direct and indirect children.
+ * WIndows conhost.exe children are filtered out.
+ * @param ph the Process to get children of
+ * @return a list of child ProcessHandles
+ */
+ public static List<ProcessHandle> getAllChildren(ProcessHandle ph) {
+ return ph.allChildren()
+ .filter(ProcessUtil::isNotWindowsConsole)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Waits for and returns the direct expected Children of a ProcessHandle.
+ * For Windows, the conhost.exe children are filtered out.
+ *
+ * @param ph the process to get the children of
+ * @param nchildren the minimum number of children to expect
+ * @return a list of ProcessHandles of the children.
+ */
+ public static List<ProcessHandle> waitForChildren(ProcessHandle ph, long nchildren) {
+ List<ProcessHandle> subprocesses = null;
+ long count = 0;
+ do {
+ if (subprocesses != null) {
+ // Only wait if this is not the first time looking
+ try {
+ Thread.sleep(500L); // It will happen but don't burn the cpu
+ } catch (InterruptedException ie) {
+ // ignore
+ }
+ }
+ subprocesses = getChildren(ph);
+ count = subprocesses.size();
+ System.out.printf(" waiting for subprocesses of %s to start," +
+ " expected: %d, current: %d%n", ph, nchildren, count);
+ } while (count < nchildren);
+ return subprocesses;
+ }
+
+ /**
+ * Waits for and returns all expected Children of a ProcessHandle.
+ * For Windows, the conhost.exe children are filtered out.
+ *
+ * @param ph the process to get the children of
+ * @param nchildren the minimum number of children to expect
+ * @return a list of ProcessHandles of the children.
+ */
+ public static List<ProcessHandle> waitForAllChildren(ProcessHandle ph, long nchildren) {
+ List<ProcessHandle> subprocesses = null;
+ long count = 0;
+ do {
+ if (subprocesses != null) {
+ // Only wait if this is not the first time looking
+ try {
+ Thread.sleep(500L); // It will happen but don't burn the cpu
+ } catch (InterruptedException ie) {
+ // ignore
+ }
+ }
+ subprocesses = getAllChildren(ph);
+ count = subprocesses.size();
+ System.out.printf(" waiting for subprocesses of %s to start," +
+ " expected: %d, current: %d%n", ph, nchildren, count);
+ } while (count < nchildren);
+ return subprocesses;
+ }
+
+ /**
+ * Destroy all children of the ProcessHandle.
+ * (Except the conhost.exe on Windows)
+ *
+ * @param p a ProcessHandle
+ * @return the ProcessHandle
+ */
+ public static ProcessHandle destroyProcessTree(ProcessHandle p) {
+ Stream<ProcessHandle> children = p.allChildren().filter(ProcessUtil::isNotWindowsConsole);
+ children.forEach(ph -> {
+ System.out.printf("destroyProcessTree destroyForcibly%n");
+ printProcess(ph);
+ ph.destroyForcibly();
+ });
+ return p;
+ }
+
+ /**
+ * The OSMXBean for this process.
+ */
+ public static final OperatingSystemMXBean osMbean =
+ (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
+
+ /**
+ * Return the CPU time of the current process according to the OperatingSystemMXBean.
+ *
+ * @return the CPU time of the current process
+ */
+ public static Duration MXBeanCpuTime() {
+ return Duration.ofNanos(osMbean.getProcessCpuTime());
+ }
+
+ /**
+ * Return true if the ProcessHandle is a Windows i586 conhost.exe process.
+ *
+ * @param p the processHandle of the Process
+ * @return Return true if the ProcessHandle is for a Windows i586 conhost.exe process
+ */
+ static boolean isWindowsConsole(ProcessHandle p) {
+ return Platform.isWindows() && p.info().command().orElse("").endsWith("C:\\Windows\\System32\\conhost.exe");
+ }
+
+ /**
+ * Return true if the ProcessHandle is NOT a Windows i586 conhost.exe process.
+ *
+ * @param p the processHandle of the Process
+ * @return Return true if the ProcessHandle is NOT for a Windows i586 conhost.exe process
+ */
+ static boolean isNotWindowsConsole(ProcessHandle p) {
+ return !isWindowsConsole(p);
+ }
+
+ /**
+ * Print a formatted string to System.out.
+ * @param format the format
+ * @param args the argument array
+ */
+ static void printf(String format, Object... args) {
+ String s = String.format(format, args);
+ System.out.print(s);
+ }
+
+ /**
+ * Print information about a process.
+ * Prints the pid, if it is alive, and information about the process.
+ * @param ph the processHandle at the top
+ */
+ static void printProcess(ProcessHandle ph) {
+ printProcess(ph, "");
+ }
+
+ /**
+ * Print information about a process.
+ * Prints the pid, if it is alive, and information about the process.
+ * @param ph the processHandle at the top
+ * @param prefix the String to prefix the output with
+ */
+ static void printProcess(ProcessHandle ph, String prefix) {
+ printf("%spid %s, alive: %s; parent: %s, %s%n", prefix,
+ ph.getPid(), ph.isAlive(), ph.parent(), ph.info());
+ }
+
+ /**
+ * Print the process hierarchy as visible via ProcessHandle.
+ * Prints the pid, if it is alive, and information about the process.
+ * @param ph the processHandle at the top
+ * @param prefix the String to prefix the output with
+ */
+ static void printDeep(ProcessHandle ph, String prefix) {
+ printProcess(ph, prefix);
+ ph.children().forEach(p -> printDeep(p, prefix + " "));
+ }
+
+ /**
+ * Use the native command to list the active processes.
+ */
+ static void logTaskList() {
+ String[] windowsArglist = {"tasklist.exe", "/v"};
+ String[] unixArglist = {"ps", "-ef"};
+
+ String[] argList = null;
+ if (Platform.isWindows()) {
+ argList = windowsArglist;
+ } else if (Platform.isLinux() || Platform.isOSX()) {
+ argList = unixArglist;
+ } else {
+ return;
+ }
+
+ ProcessBuilder pb = new ProcessBuilder(argList);
+ pb.inheritIO();
+ try {
+ Process proc = pb.start();
+ proc.waitFor();
+ } catch (IOException | InterruptedException ex) {
+ ex.printStackTrace();
+ }
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/ScaleTest.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2015, 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
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.stream.Collectors;
+import static org.testng.Assert.assertEquals;
+import org.testng.TestNG;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @summary Scalibities test for checking scability of ProcessHandle
+ * @run testng/othervm ScaleTest
+ */
+public class ScaleTest {
+ /**
+ * Scale max processes number to 5000.
+ */
+ private final static int MAX_PROCESSES_LIMIT = 1000;
+
+ /**
+ * Create test process number as 1, 2, 5, 10, 20, 50, 100, 200... until it reach
+ * setting process limit.
+ * @return iterator on how many processes can be created.
+ * @throws IOException if can't create processes.
+ */
+ @DataProvider
+ public Iterator<Object[]> processesNumbers() {
+ // Limit spawn processes number less than total limitation.
+
+ List<Object[]> testProcesses = new ArrayList<>();
+ int i = 1, j = 0;
+ while (i <= MAX_PROCESSES_LIMIT) {
+ testProcesses.add(new Object[]{i});
+ if ((j % 3) != 1)
+ i *= 2;
+ else
+ i = i * 25 / 10;
+ j++;
+ }
+ return testProcesses.iterator();
+ }
+
+ /**
+ * Start process by given number, compare created processes with
+ * ProcessHandle.children by order.
+ * @param processNum processes number that will be created
+ */
+ @Test(dataProvider = "processesNumbers")
+ public void scaleProcesses(int processNum) {
+ try {
+ ProcessBuilder pb = new ProcessBuilder();
+ pb.command("sleep", "600");
+ List<ProcessHandle> children = new ArrayList<>();
+
+ int createdProcessNum = 0;
+ for (int i = 0; i < processNum; i++) {
+ try {
+ children.add(pb.start().toHandle());
+ createdProcessNum++;
+ } catch (Throwable ignore) {
+ // Hard to control how many processes we can generate.
+ // Ignore every error when create new process
+ }
+ }
+ List<ProcessHandle> phs = ProcessHandle.current().allChildren()
+ .filter(ph -> ph.info().command().orElse("").contains("sleep"))
+ .collect(Collectors.toList());
+ assertEquals(phs.size(), createdProcessNum, "spawned processes vs allChildren");
+ assertEqualsWithoutOrder(phs, children, ProcessHandle::compareTo, processNum);
+ } finally {
+ ProcessHandle.current().children().forEach(ProcessHandle::destroyForcibly);
+ }
+ }
+
+ /**
+ * Sort two list by given comparator and compare two list without order
+ * @param actual Process handle list1
+ * @param expected Process handle list1
+ * @param comp ProcessHandle comparator for sorting
+ * @param pn number of processes
+ */
+ private void assertEqualsWithoutOrder(List<ProcessHandle> actual,
+ List<ProcessHandle> expected, Comparator<ProcessHandle> comp, int pn) {
+ Collections.sort(actual, comp);
+ Collections.sort(expected, comp);
+
+ assertEquals(actual, expected);
+ }
+
+ // Main can be used to run the tests from the command line with only testng.jar.
+ @SuppressWarnings("raw_types")
+ public static void main(String[] args) {
+ Class<?>[] testclass = {ScaleTest.class};
+ TestNG testng = new TestNG();
+ testng.setTestClasses(testclass);
+ testng.run();
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/TEST.properties Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,4 @@
+# ProcessHandle tests use TestNG
+TestNG.dirs = .
+lib.dirs = /lib/testlibrary
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ProcessHandle/TreeTest.java Fri May 29 14:04:12 2015 -0400
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2014, 2015, 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.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.concurrent.ExecutionException;
+import org.testng.Assert;
+import org.testng.TestNG;
+import org.testng.annotations.Test;
+
+/*
+ * @test
+ * @library /lib/testlibrary
+ * Test counting and JavaChild.spawning and counting of Processes.
+ * @run testng/othervm InfoTest
+ * @author Roger Riggs
+ */
+public class TreeTest extends ProcessUtil {
+ // Main can be used to run the tests from the command line with only testng.jar.
+ @SuppressWarnings("raw_types")
+ public static void main(String[] args) {
+ Class<?>[] testclass = {TreeTest.class};
+ TestNG testng = new TestNG();
+ testng.setTestClasses(testclass);
+ testng.run();
+ }
+
+ /**
+ * Test counting and spawning and counting of Processes.
+ */
+ @Test
+ public static void test1() {
+ final int MAXCHILDREN = 2;
+ List<JavaChild> spawned = new ArrayList<>();
+
+ try {
+ ProcessHandle self = ProcessHandle.current();
+
+ printf("self pid: %d%n", self.getPid());
+ printDeep(self, "");
+ long count = getChildren(self).size();
+ Assert.assertEquals(count, 0, "Start with zero children");
+
+ for (int i = 0; i < MAXCHILDREN; i++) {
+ // spawn and wait for instructions
+ spawned.add(JavaChild.spawnJavaChild("pid", "stdin"));
+ }
+
+ List<ProcessHandle> subprocesses = getChildren(self);
+ subprocesses.forEach(ProcessUtil::printProcess);
+ count = subprocesses.size();
+ Assert.assertEquals(count, MAXCHILDREN, "Wrong number of spawned children");
+
+ // Send exit command to each spawned Process
+ spawned.forEach(p -> {
+ try {
+ p.sendAction("exit", "");
+ } catch (IOException ex) {
+ Assert.fail("IOException in sendAction", ex);
+ }
+ });
+
+ // Wait for each Process to exit
+ spawned.forEach(p -> {
+ do {
+ try {
+ Assert.assertEquals(p.waitFor(), 0, "exit status incorrect");
+ break;
+ } catch (InterruptedException ex) {
+ continue; // Retry
+ }
+ } while (true);
+ });
+
+ // Verify that ProcessHandle.isAlive sees each of them as not alive
+ for (ProcessHandle ph : subprocesses) {
+ Assert.assertFalse(ph.isAlive(),
+ "ProcessHandle.isAlive for exited process: " + ph);
+ }
+
+ // Verify no current children are visible
+ count = getChildren(self).size();
+ Assert.assertEquals(count, 0, "Children destroyed, should be zero");
+
+ } catch (IOException ioe) {
+ Assert.fail("unable to spawn process", ioe);
+ } finally {
+ // Cleanup any left over processes
+ spawned.stream().map(Process::toHandle)
+ .filter(ProcessHandle::isAlive)
+ .forEach(ph -> printDeep(ph, "test1 cleanup: "));
+ destroyProcessTree(ProcessHandle.current());
+ }
+ }
+
+ /**
+ * Test counting and spawning and counting of Processes.
+ */
+ @Test
+ public static void test2() {
+ ProcessHandle p1Handle = null;
+ try {
+ ProcessHandle self = ProcessHandle.current();
+ List<ProcessHandle> initialChildren = getChildren(self);
+ long count = initialChildren.size();
+ if (count > 0) {
+ initialChildren.forEach(p -> printDeep(p, "test2 initial unexpected: "));
+ Assert.assertEquals(count, 0, "Start with zero children (except Windows conhost.exe)");
+ }
+
+ JavaChild p1 = JavaChild.spawnJavaChild("stdin");
+ p1Handle = p1.toHandle();
+ printf(" p1 pid: %d%n", p1.getPid());
+
+ int spawnNew = 3;
+ p1.sendAction("spawn", spawnNew, "stdin");
+
+ // Wait for direct children to be created and save the list
+ List<ProcessHandle> subprocesses = waitForAllChildren(p1Handle, spawnNew);
+ for (ProcessHandle ph : subprocesses) {
+ Assert.assertTrue(ph.isAlive(), "Child should be alive: " + ph);
+ }
+
+ // Each child spawns two processes and waits for commands
+ int spawnNewSub = 2;
+ p1.sendAction("child", "spawn", spawnNewSub, "stdin");
+
+ // For each spawned child, wait for its children
+ for (ProcessHandle p : subprocesses) {
+ List<ProcessHandle> grandChildren = waitForChildren(p, spawnNewSub);
+ }
+
+ List<ProcessHandle> allChildren = getAllChildren(p1Handle);
+ printf(" allChildren: %s%n",
+ allChildren.stream().map(p -> p.getPid())
+ .collect(Collectors.toList()));
+ for (ProcessHandle ph : allChildren) {
+ Assert.assertEquals(ph.isAlive(), true, "Child should be alive: " + ph);
+ }
+
+ // Closing JavaChild's InputStream will cause all children to exit
+ p1.getOutputStream().close();
+
+ for (ProcessHandle p : allChildren) {
+ try {
+ p.onExit().get(); // wait for the child to exit
+ } catch (ExecutionException e) {
+ Assert.fail("waiting for process to exit", e);
+ }
+ }
+ p1.waitFor(); // wait for spawned process to exit
+
+ List<ProcessHandle> remaining = getChildren(self);
+ remaining.forEach(ph -> Assert.assertFalse(ph.isAlive(),
+ "process should not be alive: " + ph));
+ } catch (IOException | InterruptedException t) {
+ t.printStackTrace();
+ throw new RuntimeException(t);
+ } finally {
+ // Cleanup any left over processes
+ if (p1Handle.isAlive()) {
+ printDeep(p1Handle, "test2 cleanup: ");
+ }
+ destroyProcessTree(ProcessHandle.current());
+ }
+ }
+
+ /**
+ * Test destroy of processes.
+ */
+ @Test
+ public static void test3() {
+ try {
+ ProcessHandle self = ProcessHandle.current();
+
+ JavaChild p1 = JavaChild.spawnJavaChild("stdin");
+ ProcessHandle p1Handle = p1.toHandle();
+ printf(" p1: %s%n", p1.getPid());
+ long count = getChildren(self).size();
+ Assert.assertEquals(count, 1, "Wrong number of spawned children");
+
+ int newChildren = 3;
+ // Spawn children and have them wait
+ p1.sendAction("spawn", newChildren, "stdin");
+
+ // Wait for the new processes and save the list
+ List<ProcessHandle> subprocesses = waitForAllChildren(p1Handle, newChildren);
+ printDeep(p1Handle, "allChildren");
+
+ Assert.assertEquals(subprocesses.size(), newChildren, "Wrong number of children");
+
+ p1.children().filter(TreeTest::isNotWindowsConsole)
+ .forEach(ProcessHandle::destroyForcibly);
+
+ self.children().filter(TreeTest::isNotWindowsConsole)
+ .forEach(ProcessHandle::destroyForcibly);
+
+ do {
+ Thread.sleep(500L); // It will happen but don't burn the cpu
+ Object[] children = self.allChildren()
+ .filter(TreeTest::isNotWindowsConsole)
+ .toArray();
+ count = children.length;
+ printf(" waiting for subprocesses of %s to terminate," +
+ " expected: 0, current: %d, children: %s%n", self, count,
+ Arrays.toString(children));
+ printDeep(self, "");
+ } while (count > 0);
+
+ boolean ex1 = p1.waitFor(5, TimeUnit.SECONDS);
+ Assert.assertTrue(ex1, "Subprocess should have exited: " + p1);
+
+ for (ProcessHandle p : subprocesses) {
+ Assert.assertFalse(p.isAlive(), "Destroyed process.isAlive: " + p +
+ ", parent: " + p.parent() +
+ ", info: " + p.info().toString());
+ }
+
+ } catch (IOException ioe) {
+ Assert.fail("Spawn of subprocess failed", ioe);
+ } catch (InterruptedException inte) {
+ Assert.fail("InterruptedException", inte);
+ }
+ }
+
+ /**
+ * Test (Not really a test) that dumps the list of all Processes.
+ */
+ @Test
+ public static void test4() {
+ printf(" Parent Child Info%n");
+ Stream<ProcessHandle> s = ProcessHandle.allProcesses();
+ ProcessHandle[] processes = s.toArray(ProcessHandle[]::new);
+ int len = processes.length;
+ ProcessHandle[] parent = new ProcessHandle[len];
+ Set<ProcessHandle> processesSet =
+ Arrays.stream(processes).collect(Collectors.toSet());
+ Integer[] sortindex = new Integer[len];
+ for (int i = 0; i < len; i++) {
+ sortindex[i] = i;
+ }
+ for (int i = 0; i < len; i++) {
+ parent[sortindex[i]] = processes[sortindex[i]].parent().orElse(null);
+ }
+ Arrays.sort(sortindex, (i1, i2) -> {
+ int cmp = Long.compare((parent[i1] == null ? 0L : parent[i1].getPid()),
+ (parent[i2] == null ? 0L : parent[i2].getPid()));
+ if (cmp == 0) {
+ cmp = Long.compare((processes[i1] == null ? 0L : processes[i1].getPid()),
+ (processes[i2] == null ? 0L : processes[i2].getPid()));
+ }
+ return cmp;
+ });
+ boolean fail = false;
+ for (int i = 0; i < len; i++) {
+ ProcessHandle p = processes[sortindex[i]];
+ ProcessHandle p_parent = parent[sortindex[i]];
+ ProcessHandle.Info info = p.info();
+ String indent = " ";
+ if (p_parent != null) {
+ if (!processesSet.contains(p_parent)) {
+ fail = true;
+ indent = "*** ";
+ }
+ }
+ printf("%s %7s, %7s, %s%n", indent, p_parent, p, info);
+ }
+ Assert.assertFalse(fail, "Parents missing from all Processes");
+
+ }
+
+ /**
+ * A test for scale; launch a large number (39) of subprocesses.
+ */
+ @Test
+ public static void test5() {
+ int factor = 2;
+ ProcessHandle p1Handle = null;
+ Instant start = Instant.now();
+ try {
+ JavaChild p1 = JavaChild.spawnJavaChild("stdin");
+ p1Handle = p1.toHandle();
+
+ printf("Spawning %d x %d x %d processes, pid: %d%n",
+ factor, factor, factor, p1.getPid());
+
+ // Start the first tier of subprocesses
+ p1.sendAction("spawn", factor, "stdin");
+
+ // Start the second tier of subprocesses
+ p1.sendAction("child", "spawn", factor, "stdin");
+
+ // Start the third tier of subprocesses
+ p1.sendAction("child", "child", "spawn", factor, "stdin");
+
+ int newChildren = factor * (1 + factor * (1 + factor));
+ List<ProcessHandle> children = ProcessUtil.waitForAllChildren(p1Handle, newChildren);
+
+ Assert.assertEquals(p1.children()
+ .filter(ProcessUtil::isNotWindowsConsole)
+ .count(), factor, "expected direct children");
+ Assert.assertEquals(p1.allChildren()
+ .filter(ProcessUtil::isNotWindowsConsole)
+ .count(),
+ factor * factor * factor + factor * factor + factor,
+ "expected all children");
+
+ List<ProcessHandle> subprocesses = p1.allChildren()
+ .filter(ProcessUtil::isNotWindowsConsole)
+ .collect(Collectors.toList());
+ printf(" allChildren: %s%n",
+ subprocesses.stream().map(p -> p.getPid())
+ .collect(Collectors.toList()));
+
+ p1.getOutputStream().close(); // Close stdin for the controlling p1
+ p1.waitFor();
+ } catch (InterruptedException | IOException ex) {
+ Assert.fail("Unexpected Exception", ex);
+ } finally {
+ printf("Duration: %s%n", Duration.between(start, Instant.now()));
+ // Cleanup any left over processes
+ if (p1Handle.isAlive()) {
+ printDeep(p1Handle, "test5 cleanup: ");
+ }
+ destroyProcessTree(ProcessHandle.current());
+ }
+ }
+
+}