# HG changeset patch # User rriggs # Date 1432922652 14400 # Node ID d2408e757489149e6f2600938203a4d7c7cdeb58 # Parent 6027a68229dc4780751156cafbc31252a32ff5f2 8077350: JEP 102 Process API Updates Implementation Reviewed-by: chegar, plevart, psandoz, darcy, martin, alanb diff -r 6027a68229dc -r d2408e757489 jdk/make/mapfiles/libjava/mapfile-vers --- 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 diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/macosx/native/libjava/ProcessHandleImpl_macosx.c --- /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 +#include +#include +#include +#include +#include + +#include + +/** + * 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//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); +} + diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/share/classes/java/lang/Process.java --- 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. * *

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. * - *

By default, the created subprocess does not have its own terminal + *

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. * *

Where desired, - * subprocess I/O can also be redirected + * process I/O can also be redirected * using methods of the {@link ProcessBuilder} class. * - *

The subprocess is not killed when there are no more references to - * the {@code Process} object, but rather the subprocess + *

The process is not killed when there are no more references to + * the {@code Process} object, but rather the process * continues executing asynchronously. * - *

There is no requirement that a process represented by a {@code + *

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. * *

As of 1.5, {@link ProcessBuilder#start()} is the preferred way * to create a {@code Process}. * + *

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. * - *

If the standard input of the subprocess has been redirected using + *

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. * - *

If the standard output of the subprocess has been redirected using + *

If the standard output of the process has been redirected using * {@link ProcessBuilder#redirectOutput(Redirect) * ProcessBuilder.redirectOutput} * then this method will return a * null input stream. * - *

Otherwise, if the standard error of the subprocess has been + *

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. * *

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. * - *

If the standard error of the subprocess has been redirected using + *

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. * - *

If the subprocess has already terminated then this method returns + *

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. + *

+ * 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. + *

+ * The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is + * {@link java.util.concurrent.CompletableFuture#complete completed} + * when the process has terminated. + *

+ * Invoking this method on {@code Process} objects returned by + * {@link ProcessBuilder#start} and {@link Runtime#exec} forcibly terminate + * the process. * - *

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. - * - *

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. + *

+ * 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} 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. + *

+ * 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. + *

+ * If the process is {@link #isAlive not alive} the {@link CompletableFuture} + * returned has been {@link java.util.concurrent.CompletableFuture#complete completed}. + *

+ * Processes returned from {@link ProcessBuilder#start} override the + * default implementation to provide an efficient mechanism to wait + * for process exit. + *

+ * @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. + *
+ * For example, launching a process to compare two files and get a boolean if they are identical: + *

 {@code   Process p = new ProcessBuilder("cmp", "f1", "f2").start();
+     *    Future identical = p.onExit().thenApply(p1 -> p1.exitValue() == 0);
+     *    ...
+     *    if (identical.get()) { ... }
+     * }
+ * + * @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. + *

+ * 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. + *

+ * 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: + *

{@code
+     *    public CompletableFuture onExit() {
+     *       return delegate.onExit().thenApply(p -> this);
+     *    }
+     * }
+ * + * @return a new {@code CompletableFuture} for the Process + * + * @since 1.9 + */ + public CompletableFuture 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. + * + *

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. + *

+ * Note that processes are created and terminate asynchronously. + * There is no guarantee that a process is {@link #isAlive alive}. + * + * + * @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 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. + *

+ * Note that processes are created and terminate asynchronously. + * There is no guarantee that a process is {@link #isAlive alive}. + * + * + * @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 allChildren() { + return toHandle().allChildren(); + } + + } diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/share/classes/java/lang/ProcessHandle.java --- /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. + *

+ * 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. + *

+ * 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()}. + *

+ * 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. + * + *

+ * A {@link java.util.concurrent.CompletableFuture} available from {@link #onExit} + * can be used to wait for process termination, and possibly trigger dependent + * actions. + *

+ * 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. + *

+ * @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 { + + /** + * 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} for an existing native process. + * + * @param pid a native process ID + * @return an {@code Optional} 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 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} for the parent process. + * Note that Processes in a zombie state usually don't have a parent. + * + * @return an {@code Optional} 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 parent(); + + /** + * Returns a snapshot of the current direct children of the process. + * A process that is {@link #isAlive not alive} has zero children. + *

+ * Note that processes are created and terminate asynchronously. + * There is no guarantee that a process is {@link #isAlive alive}. + * + * + * @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 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. + *

+ * Note that processes are created and terminate asynchronously. + * There is no guarantee that a process is {@link #isAlive alive}. + * + * + * @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 allChildren(); + + /** + * Returns a snapshot of all processes visible to the current process. + *

+ * 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. + * + * + * @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 allProcesses() { + return ProcessHandleImpl.children(0); + } + + /** + * Returns a snapshot of information about the process. + * + *

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} 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} of the executable pathname + * of the process + */ + public Optional command(); + + /** + * Returns an array of Strings of the arguments of the process. + * + * @return an {@code Optional} of the arguments of the process + */ + public Optional arguments(); + + /** + * Returns the start time of the process. + * + * @return an {@code Optional} of the start time of the process + */ + public Optional startInstant(); + + /** + * Returns the total cputime accumulated of the process. + * + * @return an {@code Optional} for the accumulated total cputime + */ + public Optional totalCpuDuration(); + + /** + * Return the user of the process. + * + * @return an {@code Optional} for the user of the process + */ + public Optional user(); + } + + /** + * Returns a {@code CompletableFuture} 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. + *

+ * 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. + *

+ * 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} for the ProcessHandle + * + * @throws IllegalStateException if the process is the current process + */ + CompletableFuture 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. + *

+ * The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is + * {@link java.util.concurrent.CompletableFuture#complete completed} + * when the process has terminated. + *

+ * 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. + *

+ * The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is + * {@link java.util.concurrent.CompletableFuture#complete completed} + * when the process has terminated. + *

+ * 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); + +} diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/share/classes/java/lang/ProcessHandleImpl.java --- /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) () -> { + + 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 { + final boolean isReaping; + + ExitCompletion(boolean isReaping) { + this.isReaping = isReaping; + } + } + + private static final ConcurrentMap + 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 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 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 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 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 parent() { + return parent(pid); + } + + @Override + public Stream 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 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 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 command() { + return Optional.ofNullable(command); + } + + @Override + public Optional arguments() { + return Optional.ofNullable(arguments); + } + + @Override + public Optional startInstant() { + return (startTime > 0) + ? Optional.of(Instant.ofEpochMilli(startTime)) + : Optional.empty(); + } + + @Override + public Optional totalCpuDuration() { + return (totalTime != -1) + ? Optional.of(Duration.ofNanos(totalTime)) + : Optional.empty(); + } + + @Override + public Optional 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(); + } + } +} diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/share/classes/java/lang/RuntimePermission.java --- 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. * + * + * manageProcess + * Native process termination and information about processes + * {@link ProcessHandle}. + * Allows code to identify and terminate processes that it did not create. + * * * * localeServiceProvider diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/solaris/native/libjava/ProcessHandleImpl_solaris.c --- /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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * 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//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//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); +} + diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/unix/classes/java/lang/ProcessImpl.java --- 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) () -> - helperPath(System.getProperty("java.home"), - System.getProperty("os.arch")) + (PrivilegedAction) () -> + helperPath(System.getProperty("java.home"), + System.getProperty("os.arch")) ); } LaunchMechanism launchMechanism() { return AccessController.doPrivileged( - (PrivilegedAction) () -> { - 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) () -> { + 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) () -> System.getProperty("os.name") + (PrivilegedAction) () -> 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) () -> { - - 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) () -> { @@ -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 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; diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/unix/native/libjava/ProcessHandleImpl_unix.c --- /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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + * 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//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//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; + } +} + + diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/unix/native/libjava/ProcessImpl_md.c --- 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); -} diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/windows/classes/java/lang/ProcessImpl.java --- 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() { @@ -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 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) diff -r 6027a68229dc -r d2408e757489 jdk/src/java.base/windows/native/libjava/ProcessHandleImpl_win.c --- /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 +#include + +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//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); +} diff -r 6027a68229dc -r d2408e757489 jdk/test/TEST.ROOT --- 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 diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessBuilder/Basic.java --- 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);} diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/Basic.java --- /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 self1 = ProcessHandle.of(pid); + assertEquals(self1.get(), self, + "ProcessHandle.of(x.getPid()) should be equal getPid() %d: %d"); + + Optional 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 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 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 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(); + } + +} diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/InfoTest.java --- /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 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 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 dur1 = ProcessHandle.current().info().totalCpuDuration(); + + Duration myCputime2 = ProcessUtil.MXBeanCpuTime(); + + Optional 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 list = new ArrayList<>(); + list.add(command); + for (String arg : args) + list.add(arg); + pb.command(list); + return pb.start(); + } +} diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/JavaChild.java --- /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 children = new ArrayList<>(); + private static final Set 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 onExit() { + return delegate.onExit(); + } + @Override + public String toString() { + return "delegate: " + delegate.toString(); + } + + public CompletableFuture 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 forEachOutputLine(Consumer consumer) { + final CompletableFuture 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 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 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 "); + System.err.println(" cputime"); + System.err.println(" stdin - read commands from stdin"); + System.err.println(" sleep "); + System.err.println(" spawn 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 "); + 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(); + } + + } +} diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/OnExitTest.java --- /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 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 processes = new ConcurrentHashMap<>(); + List 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 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 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 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()); + } + } + +} diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/PermissionTest.java --- /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("<>", "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); + } +} diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/ProcessUtil.java --- /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 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 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 waitForChildren(ProcessHandle ph, long nchildren) { + List 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 waitForAllChildren(ProcessHandle ph, long nchildren) { + List 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 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(); + } + } +} diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/ScaleTest.java --- /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 processesNumbers() { + // Limit spawn processes number less than total limitation. + + List 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 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 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 actual, + List expected, Comparator 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(); + } +} diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/TEST.properties --- /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 + diff -r 6027a68229dc -r d2408e757489 jdk/test/java/lang/ProcessHandle/TreeTest.java --- /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 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 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 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 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 grandChildren = waitForChildren(p, spawnNewSub); + } + + List 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 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 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 s = ProcessHandle.allProcesses(); + ProcessHandle[] processes = s.toArray(ProcessHandle[]::new); + int len = processes.length; + ProcessHandle[] parent = new ProcessHandle[len]; + Set 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 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 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()); + } + } + +}