8077350: JEP 102 Process API Updates Implementation

Reviewed-by: chegar, plevart, psandoz, darcy, martin, alanb
This commit is contained in:
Roger Riggs 2015-05-29 14:04:12 -04:00
parent 5fbfe34bf6
commit 103d99baf1
23 changed files with 5508 additions and 235 deletions

View File

@ -166,6 +166,16 @@ SUNWprivate_1.1 {
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 @@ SUNWprivate_1.1 {
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 @@ SUNWprivate_1.1 {
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

View File

@ -0,0 +1,401 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni.h"
#include "jni_util.h"
#include "java_lang_ProcessHandleImpl.h"
#include "java_lang_ProcessHandleImpl_Info.h"
#include <stdio.h>
#include <errno.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/sysctl.h>
/**
* Implementations of ProcessHandleImpl functions for MAC OS X;
* are NOT common to all Unix variants.
*/
static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid);
static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid);
/*
* Common Unix function to lookup the uid and return the user name.
*/
extern jstring uidToUser(JNIEnv* env, uid_t uid);
/* Field id for jString 'command' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_commandID;
/* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_argumentsID;
/* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_totalTimeID;
/* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_startTimeID;
/* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_userID;
/* static value for clock ticks per second. */
static long clock_ticks_per_second;
/**************************************************************
* Static method to initialize field IDs and the ticks per second rate.
*
* Class: java_lang_ProcessHandleImpl_Info
* Method: initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
(JNIEnv *env, jclass clazz) {
CHECK_NULL(ProcessHandleImpl_Info_commandID =
(*env)->GetFieldID(env, clazz, "command", "Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_argumentsID =
(*env)->GetFieldID(env, clazz, "arguments", "[Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_totalTimeID =
(*env)->GetFieldID(env, clazz, "totalTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_startTimeID =
(*env)->GetFieldID(env, clazz, "startTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_userID =
(*env)->GetFieldID(env, clazz, "user", "Ljava/lang/String;"));
clock_ticks_per_second = sysconf(_SC_CLK_TCK);
}
/*
* Returns the parent pid of the requested pid.
*
* Class: java_lang_ProcessHandleImpl
* Method: parent0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
(JNIEnv *env, jobject obj, jlong jpid) {
pid_t pid = (pid_t) jpid;
pid_t ppid = -1;
if (pid == getpid()) {
ppid = getppid();
} else {
const pid_t pid = (pid_t) jpid;
struct kinfo_proc kp;
size_t bufSize = sizeof kp;
// Read the process info for the specific pid
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) < 0) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "sysctl failed");
return -1;
}
ppid = (bufSize > 0 && kp.kp_proc.p_pid == pid) ? kp.kp_eproc.e_ppid : -1;
}
return (jlong) ppid;
}
/*
* Returns the children of the requested pid and optionally each parent.
*
* Class: java_lang_ProcessHandleImpl
* Method: getProcessPids0
* Signature: (J[J[J)I
*
* Use sysctl to accumulate any process whose parent pid is zero or matches.
* The resulting pids are stored into the array of longs.
* The number of pids is returned if they all fit.
* If the parentArray is non-null, store the parent pid.
* If the array is too short, excess pids are not stored and
* the desired length is returned.
*/
JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
(JNIEnv *env, jclass clazz, jlong jpid,
jlongArray jarray, jlongArray jparentArray)
{
size_t count = 0;
jlong* pids = NULL;
jlong* ppids = NULL;
size_t parentArraySize = 0;
size_t arraySize = 0;
size_t bufSize = 0;
pid_t pid = (pid_t) jpid;
arraySize = (*env)->GetArrayLength(env, jarray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (jparentArray != NULL) {
parentArraySize = (*env)->GetArrayLength(env, jparentArray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (arraySize != parentArraySize) {
JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
return 0;
}
}
// Get buffer size needed to read all processes
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
if (sysctl(mib, 4, NULL, &bufSize, NULL, 0) < 0) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "sysctl failed");
return -1;
}
// Allocate buffer big enough for all processes
void *buffer = malloc(bufSize);
if (buffer == NULL) {
JNU_ThrowOutOfMemoryError(env, "malloc failed");
return -1;
}
// Read process info for all processes
if (sysctl(mib, 4, buffer, &bufSize, NULL, 0) < 0) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "sysctl failed");
free(buffer);
return -1;
}
do { // Block to break out of on Exception
struct kinfo_proc *kp = (struct kinfo_proc *) buffer;
unsigned long nentries = bufSize / sizeof (struct kinfo_proc);
long i;
pids = (*env)->GetLongArrayElements(env, jarray, NULL);
if (pids == NULL) {
break;
}
if (jparentArray != NULL) {
ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
if (ppids == NULL) {
break;
}
}
// Process each entry in the buffer
for (i = nentries; --i >= 0; ++kp) {
if (pid == 0 || kp->kp_eproc.e_ppid == pid) {
if (count < arraySize) {
// Only store if it fits
pids[count] = (jlong) kp->kp_proc.p_pid;
if (ppids != NULL) {
// Store the parentPid
ppids[count] = (jlong) kp->kp_eproc.e_ppid;
}
}
count++; // Count to tabulate size needed
}
}
} while (0);
if (pids != NULL) {
(*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
}
if (ppids != NULL) {
(*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
}
free(buffer);
// If more pids than array had size for; count will be greater than array size
return count;
}
/**************************************************************
* Implementation of ProcessHandleImpl_Info native methods.
*/
/*
* Fill in the Info object from the OS information about the process.
*
* Class: java_lang_ProcessHandleImpl
* Method: info0
* Signature: (J)I
*/
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
(JNIEnv *env, jobject jinfo, jlong jpid) {
pid_t pid = (pid_t) jpid;
getStatInfo(env, jinfo, pid);
getCmdlineInfo(env, jinfo, pid);
}
/**
* Read /proc/<pid>/stat and fill in the fields of the Info object.
* The executable name, plus the user, system, and start times are gathered.
*/
static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t jpid) {
jlong totalTime; // nanoseconds
unsigned long long startTime; // microseconds
const pid_t pid = (pid_t) jpid;
struct kinfo_proc kp;
size_t bufSize = sizeof kp;
// Read the process info for the specific pid
int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid};
if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) < 0) {
if (errno == EINVAL) {
return;
} else {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "sysctl failed");
}
return;
}
// Convert the UID to the username
jstring name = NULL;
CHECK_NULL((name = uidToUser(env, kp.kp_eproc.e_ucred.cr_uid)));
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, name);
JNU_CHECK_EXCEPTION(env);
startTime = kp.kp_proc.p_starttime.tv_sec * 1000 +
kp.kp_proc.p_starttime.tv_usec / 1000;
(*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
JNU_CHECK_EXCEPTION(env);
// Get cputime if for current process
if (pid == getpid()) {
struct rusage usage;
if (getrusage(RUSAGE_SELF, &usage) != 0) {
return;
}
jlong microsecs =
usage.ru_utime.tv_sec * 1000 * 1000 + usage.ru_utime.tv_usec +
usage.ru_stime.tv_sec * 1000 * 1000 + usage.ru_stime.tv_usec;
totalTime = microsecs * 1000;
(*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, totalTime);
JNU_CHECK_EXCEPTION(env);
}
}
/**
* Construct the argument array by parsing the arguments from the sequence of arguments.
*/
static int fillArgArray(JNIEnv *env, jobject jinfo, int nargs,
const char *cp, const char *argsEnd) {
jstring str = NULL;
jobject argsArray;
int i;
if (nargs < 1) {
return 0;
}
// Create a String array for nargs-1 elements
CHECK_NULL_RETURN((argsArray = (*env)->NewObjectArray(env,
nargs - 1, JNU_ClassString(env), NULL)), -1);
for (i = 0; i < nargs - 1; i++) {
// skip to the next argument; omits arg[0]
cp += strnlen(cp, (argsEnd - cp)) + 1;
if (cp > argsEnd || *cp == '\0') {
return -2; // Off the end pointer or an empty argument is an error
}
CHECK_NULL_RETURN((str = JNU_NewStringPlatform(env, cp)), -1);
(*env)->SetObjectArrayElement(env, argsArray, i, str);
JNU_CHECK_EXCEPTION_RETURN(env, -3);
}
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_argumentsID, argsArray);
JNU_CHECK_EXCEPTION_RETURN(env, -4);
return 0;
}
/**
* Retrieve the command and arguments for the process and store them
* into the Info object.
*/
static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
int mib[3], maxargs, nargs, i;
size_t size;
char *args, *cp, *sp, *np;
// Get the maximum size of the arguments
mib[0] = CTL_KERN;
mib[1] = KERN_ARGMAX;
size = sizeof(maxargs);
if (sysctl(mib, 2, &maxargs, &size, NULL, 0) == -1) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "sysctl failed");
return;
}
// Allocate an args buffer and get the arguments
args = (char *)malloc(maxargs);
if (args == NULL) {
JNU_ThrowOutOfMemoryError(env, "malloc failed");
return;
}
do { // a block to break out of on error
char *argsEnd;
jstring str = NULL;
mib[0] = CTL_KERN;
mib[1] = KERN_PROCARGS2;
mib[2] = pid;
size = (size_t) maxargs;
if (sysctl(mib, 3, args, &size, NULL, 0) == -1) {
if (errno != EINVAL) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "sysctl failed");
}
break;
}
memcpy(&nargs, args, sizeof(nargs));
cp = &args[sizeof(nargs)]; // Strings start after nargs
argsEnd = &args[size];
// Store the command executable path
if ((str = JNU_NewStringPlatform(env, cp)) == NULL) {
break;
}
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, str);
if ((*env)->ExceptionCheck(env)) {
break;
}
// Skip trailing nulls after the executable path
for (cp = cp + strnlen(cp, argsEnd - cp); cp < argsEnd; cp++) {
if (*cp != '\0') {
break;
}
}
fillArgArray(env, jinfo, nargs, cp, argsEnd);
} while (0);
// Free the arg buffer
free(args);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 1995, 2012, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -26,25 +26,31 @@
package java.lang;
import java.io.*;
import java.lang.ProcessBuilder.Redirect;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
/**
* {@code Process} provides control of native processes started by
* ProcessBuilder.start and Runtime.exec.
* The class provides methods for performing input from the process, performing
* output to the process, waiting for the process to complete,
* checking the exit status of the process, and destroying (killing)
* the process.
* The {@link ProcessBuilder#start()} and
* {@link Runtime#exec(String[],String[],File) Runtime.exec}
* methods create a native process and return an instance of a
* subclass of {@code Process} that can be used to control the process
* and obtain information about it. The class {@code Process}
* provides methods for performing input from the process, performing
* output to the process, waiting for the process to complete,
* checking the exit status of the process, and destroying (killing)
* the process.
* and obtain information about it.
*
* <p>The methods that create processes may not work well for special
* processes on certain native platforms, such as native windowing
* processes, daemon processes, Win16/DOS processes on Microsoft
* Windows, or shell scripts.
*
* <p>By default, the created subprocess does not have its own terminal
* <p>By default, the created process does not have its own terminal
* or console. All its standard I/O (i.e. stdin, stdout, stderr)
* operations will be redirected to the parent process, where they can
* be accessed via the streams obtained using the methods
@ -52,35 +58,49 @@ import java.util.concurrent.TimeUnit;
* {@link #getInputStream()}, and
* {@link #getErrorStream()}.
* The parent process uses these streams to feed input to and get output
* from the subprocess. Because some native platforms only provide
* from the process. Because some native platforms only provide
* limited buffer size for standard input and output streams, failure
* to promptly write the input stream or read the output stream of
* the subprocess may cause the subprocess to block, or even deadlock.
* the process may cause the process to block, or even deadlock.
*
* <p>Where desired, <a href="ProcessBuilder.html#redirect-input">
* subprocess I/O can also be redirected</a>
* process I/O can also be redirected</a>
* using methods of the {@link ProcessBuilder} class.
*
* <p>The subprocess is not killed when there are no more references to
* the {@code Process} object, but rather the subprocess
* <p>The process is not killed when there are no more references to
* the {@code Process} object, but rather the process
* continues executing asynchronously.
*
* <p>There is no requirement that a process represented by a {@code
* <p>There is no requirement that the process represented by a {@code
* Process} object execute asynchronously or concurrently with respect
* to the Java process that owns the {@code Process} object.
*
* <p>As of 1.5, {@link ProcessBuilder#start()} is the preferred way
* to create a {@code Process}.
*
* <p>Subclasses of Process should override the {@link #onExit()} and
* {@link #toHandle()} methods to provide a fully functional Process including the
* {@link #getPid() process id},
* {@link #info() information about the process},
* {@link #children() direct children}, and
* {@link #allChildren() direct and indirect children} of the process.
* Delegating to the underlying Process or ProcessHandle is typically
* easiest and most efficient.
*
* @since 1.0
*/
public abstract class Process {
/**
* Default constructor for Process.
*/
public Process() {}
/**
* Returns the output stream connected to the normal input of the
* subprocess. Output to the stream is piped into the standard
* process. Output to the stream is piped into the standard
* input of the process represented by this {@code Process} object.
*
* <p>If the standard input of the subprocess has been redirected using
* <p>If the standard input of the process has been redirected using
* {@link ProcessBuilder#redirectInput(Redirect)
* ProcessBuilder.redirectInput}
* then this method will return a
@ -90,42 +110,42 @@ public abstract class Process {
* output stream to be buffered.
*
* @return the output stream connected to the normal input of the
* subprocess
* process
*/
public abstract OutputStream getOutputStream();
/**
* Returns the input stream connected to the normal output of the
* subprocess. The stream obtains data piped from the standard
* process. The stream obtains data piped from the standard
* output of the process represented by this {@code Process} object.
*
* <p>If the standard output of the subprocess has been redirected using
* <p>If the standard output of the process has been redirected using
* {@link ProcessBuilder#redirectOutput(Redirect)
* ProcessBuilder.redirectOutput}
* then this method will return a
* <a href="ProcessBuilder.html#redirect-output">null input stream</a>.
*
* <p>Otherwise, if the standard error of the subprocess has been
* <p>Otherwise, if the standard error of the process has been
* redirected using
* {@link ProcessBuilder#redirectErrorStream(boolean)
* ProcessBuilder.redirectErrorStream}
* then the input stream returned by this method will receive the
* merged standard output and the standard error of the subprocess.
* merged standard output and the standard error of the process.
*
* <p>Implementation note: It is a good idea for the returned
* input stream to be buffered.
*
* @return the input stream connected to the normal output of the
* subprocess
* process
*/
public abstract InputStream getInputStream();
/**
* Returns the input stream connected to the error output of the
* subprocess. The stream obtains data piped from the error output
* process. The stream obtains data piped from the error output
* of the process represented by this {@code Process} object.
*
* <p>If the standard error of the subprocess has been redirected using
* <p>If the standard error of the process has been redirected using
* {@link ProcessBuilder#redirectError(Redirect)
* ProcessBuilder.redirectError} or
* {@link ProcessBuilder#redirectErrorStream(boolean)
@ -137,19 +157,19 @@ public abstract class Process {
* 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 @@ public abstract class Process {
/**
* Causes the current thread to wait, if necessary, until the
* subprocess represented by this {@code Process} object has
* process represented by this {@code Process} object has
* terminated, or the specified waiting time elapses.
*
* <p>If the subprocess has already terminated then this method returns
* <p>If the process has already terminated then this method returns
* immediately with the value {@code true}. If the process has not
* terminated and the timeout value is less than, or equal to, zero, then
* this method returns immediately with the value {@code false}.
@ -176,8 +196,8 @@ public abstract class Process {
*
* @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 @@ public abstract class Process {
}
/**
* Returns the exit value for the subprocess.
* Returns the exit value for the process.
*
* @return the exit value of the subprocess represented by this
* @return the exit value of the process represented by this
* {@code Process} object. By convention, the value
* {@code 0} indicates normal termination.
* @throws IllegalThreadStateException if the subprocess represented
* @throws IllegalThreadStateException if the process represented
* by this {@code Process} object has not yet terminated
*/
public abstract int exitValue();
/**
* Kills the subprocess. Whether the subprocess represented by this
* {@code Process} object is forcibly terminated or not is
* Kills the process.
* Whether the process represented by this {@code Process} object is
* {@link #supportsNormalTermination normally terminated} or not is
* implementation dependent.
* Forcible process destruction is defined as the immediate termination of a
* process, whereas normal termination allows the process to shut down cleanly.
* If the process is not alive, no action is taken.
* <p>
* The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is
* {@link java.util.concurrent.CompletableFuture#complete completed}
* when the process has terminated.
*/
public abstract void destroy();
/**
* Kills the subprocess. The subprocess represented by this
* Kills the process forcibly. The process represented by this
* {@code Process} object is forcibly terminated.
* Forcible process destruction is defined as the immediate termination of a
* process, whereas normal termination allows the process to shut down cleanly.
* If the process is not alive, no action is taken.
* <p>
* The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is
* {@link java.util.concurrent.CompletableFuture#complete completed}
* when the process has terminated.
* <p>
* Invoking this method on {@code Process} objects returned by
* {@link ProcessBuilder#start} and {@link Runtime#exec} forcibly terminate
* the process.
*
* <p>The default implementation of this method invokes {@link #destroy}
* and so may not forcibly terminate the process. Concrete implementations
* of this class are strongly encouraged to override this method with a
* compliant implementation. Invoking this method on {@code Process}
* objects returned by {@link ProcessBuilder#start} and
* {@link Runtime#exec} will forcibly terminate the process.
*
* <p>Note: The subprocess may not terminate immediately.
* @implSpec
* The default implementation of this method invokes {@link #destroy}
* and so may not forcibly terminate the process.
* @implNote
* Concrete implementations of this class are strongly encouraged to override
* this method with a compliant implementation.
* @apiNote
* The process may not terminate immediately.
* i.e. {@code isAlive()} may return true for a brief period
* after {@code destroyForcibly()} is called. This method
* may be chained to {@code waitFor()} if needed.
*
* @return the {@code Process} object representing the
* subprocess to be forcibly destroyed.
* process forcibly destroyed
* @since 1.8
*/
public Process destroyForcibly() {
@ -247,10 +286,36 @@ public abstract class Process {
}
/**
* Tests whether the subprocess represented by this {@code Process} is
* Returns {@code true} if the implementation of {@link #destroy} is to
* normally terminate the process,
* Returns {@code false} if the implementation of {@code destroy}
* forcibly and immediately terminates the process.
* <p>
* Invoking this method on {@code Process} objects returned by
* {@link ProcessBuilder#start} and {@link Runtime#exec} return
* {@code true} or {@code false} depending on the platform implementation.
*
* @implSpec
* This implementation throws an instance of
* {@link java.lang.UnsupportedOperationException} and performs no other action.
*
* @return {@code true} if the implementation of {@link #destroy} is to
* normally terminate the process;
* otherwise, {@link #destroy} forcibly terminates the process
* @throws UnsupportedOperationException if the Process implementation
* does not support this operation
* @since 1.9
*/
public boolean supportsNormalTermination() {
throw new UnsupportedOperationException(this.getClass()
+ ".supportsNormalTermination() not supported" );
}
/**
* Tests whether the process represented by this {@code Process} is
* alive.
*
* @return {@code true} if the subprocess represented by this
* @return {@code true} if the process represented by this
* {@code Process} object has not yet terminated.
* @since 1.8
*/
@ -264,16 +329,222 @@ public abstract class Process {
}
/**
* Returns the native process id of the subprocess.
* The native process id is an identification number that the operating
* Returns the native process ID of the process.
* The native process ID is an identification number that the operating
* system assigns to the process.
*
* @return the native process id of the subprocess
* @implSpec
* The implementation of this method returns the process id as:
* {@link #toHandle toHandle().getPid()}.
*
* @return the native process id of the process
* @throws UnsupportedOperationException if the Process implementation
* does not support this operation
* does not support this operation
* @since 1.9
*/
public long getPid() {
throw new UnsupportedOperationException();
return toHandle().getPid();
}
/**
* Returns a {@code CompletableFuture<Process>} for the termination of the Process.
* The {@link java.util.concurrent.CompletableFuture} provides the ability
* to trigger dependent functions or actions that may be run synchronously
* or asynchronously upon process termination.
* When the process terminates the CompletableFuture is
* {@link java.util.concurrent.CompletableFuture#complete completed} regardless
* of the exit status of the process.
* <p>
* Calling {@code onExit().get()} waits for the process to terminate and returns
* the Process. The future can be used to check if the process is
* {@link java.util.concurrent.CompletableFuture#isDone done} or to
* {@link java.util.concurrent.CompletableFuture#get() wait} for it to terminate.
* {@link java.util.concurrent.CompletableFuture#cancel(boolean) Cancelling}
* the CompletableFuture does not affect the Process.
* <p>
* If the process is {@link #isAlive not alive} the {@link CompletableFuture}
* returned has been {@link java.util.concurrent.CompletableFuture#complete completed}.
* <p>
* Processes returned from {@link ProcessBuilder#start} override the
* default implementation to provide an efficient mechanism to wait
* for process exit.
* <p>
* @apiNote
* Using {@link #onExit() onExit} is an alternative to
* {@link #waitFor() waitFor} that enables both additional concurrency
* and convenient access to the result of the Process.
* Lambda expressions can be used to evaluate the result of the Process
* execution.
* If there is other processing to be done before the value is used
* then {@linkplain #onExit onExit} is a convenient mechanism to
* free the current thread and block only if and when the value is needed.
* <br>
* For example, launching a process to compare two files and get a boolean if they are identical:
* <pre> {@code Process p = new ProcessBuilder("cmp", "f1", "f2").start();
* Future<Boolean> identical = p.onExit().thenApply(p1 -> p1.exitValue() == 0);
* ...
* if (identical.get()) { ... }
* }</pre>
*
* @implSpec
* This implementation executes {@link #waitFor()} in a separate thread
* repeatedly until it returns successfully. If the execution of
* {@code waitFor} is interrupted, the thread's interrupt status is preserved.
* <p>
* When {@link #waitFor()} returns successfully the CompletableFuture is
* {@link java.util.concurrent.CompletableFuture#complete completed} regardless
* of the exit status of the process.
*
* This implementation may consume a lot of memory for thread stacks if a
* large number of processes are waited for concurrently.
* <p>
* External implementations should override this method and provide
* a more efficient implementation. For example, to delegate to the underlying
* process, it can do the following:
* <pre>{@code
* public CompletableFuture<Process> onExit() {
* return delegate.onExit().thenApply(p -> this);
* }
* }</pre>
*
* @return a new {@code CompletableFuture<Process>} for the Process
*
* @since 1.9
*/
public CompletableFuture<Process> onExit() {
return CompletableFuture.supplyAsync(this::waitForInternal);
}
/**
* Wait for the process to exit by calling {@code waitFor}.
* If the thread is interrupted, remember the interrupted state to
* be restored before returning. Use ForkJoinPool.ManagedBlocker
* so that the number of workers in case ForkJoinPool is used is
* compensated when the thread blocks in waitFor().
*
* @return the Process
*/
private Process waitForInternal() {
boolean interrupted = false;
while (true) {
try {
ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker() {
@Override
public boolean block() throws InterruptedException {
waitFor();
return true;
}
@Override
public boolean isReleasable() {
return !isAlive();
}
});
break;
} catch (InterruptedException x) {
interrupted = true;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
return this;
}
/**
* Returns a ProcessHandle for the Process.
*
* {@code Process} objects returned by {@link ProcessBuilder#start} and
* {@link Runtime#exec} implement {@code toHandle} as the equivalent of
* {@link ProcessHandle#of(long) ProcessHandle.of(pid)} including the
* check for a SecurityManager and {@code RuntimePermission("manageProcess")}.
*
* @implSpec
* This implementation throws an instance of
* {@link java.lang.UnsupportedOperationException} and performs no other action.
* Subclasses should override this method to provide a ProcessHandle for the
* process. The methods {@link #getPid}, {@link #info}, {@link #children},
* and {@link #allChildren}, unless overridden, operate on the ProcessHandle.
*
* @return Returns a ProcessHandle for the Process
* @throws UnsupportedOperationException if the Process implementation
* does not support this operation
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
* @since 1.9
*/
public ProcessHandle toHandle() {
throw new UnsupportedOperationException(this.getClass()
+ ".toHandle() not supported");
}
/**
* Returns a snapshot of information about the process.
*
* <p> An {@link ProcessHandle.Info} instance has various accessor methods
* that return information about the process, if the process is alive and
* the information is available, otherwise {@code null} is returned.
*
* @implSpec
* This implementation returns information about the process as:
* {@link #toHandle toHandle().info()}.
*
* @return a snapshot of information about the process, always non-null
* @throws UnsupportedOperationException if the Process implementation
* does not support this operation
* @since 1.9
*/
public ProcessHandle.Info info() {
return toHandle().info();
}
/**
* Returns a snapshot of the direct children of the process.
* A process that is {@link #isAlive not alive} has zero children.
* <p>
* <em>Note that processes are created and terminate asynchronously.
* There is no guarantee that a process is {@link #isAlive alive}.
* </em>
*
* @implSpec
* This implementation returns the direct children as:
* {@link #toHandle toHandle().children()}.
*
* @return a Stream of ProcessHandles for processes that are direct children
* of the process
* @throws UnsupportedOperationException if the Process implementation
* does not support this operation
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
* @since 1.9
*/
public Stream<ProcessHandle> children() {
return toHandle().children();
}
/**
* Returns a snapshot of the direct and indirect children of the process.
* A process that is {@link #isAlive not alive} has zero children.
* <p>
* <em>Note that processes are created and terminate asynchronously.
* There is no guarantee that a process is {@link #isAlive alive}.
* </em>
*
* @implSpec
* This implementation returns all children as:
* {@link #toHandle toHandle().allChildren()}.
*
* @return a Stream of ProcessHandles for processes that are direct and
* indirect children of the process
* @throws UnsupportedOperationException if the Process implementation
* does not support this operation
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
* @since 1.9
*/
public Stream<ProcessHandle> allChildren() {
return toHandle().allChildren();
}
}

View File

@ -0,0 +1,361 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang;
import java.time.Duration;
import java.time.Instant;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
/**
* ProcessHandle identifies and provides control of native processes. Each
* individual process can be monitored for liveness, list its children,
* get information about the process or destroy it.
* By comparison, {@link java.lang.Process Process} instances were started
* by the current process and additionally provide access to the process
* input, output, and error streams.
* <p>
* The native process ID is an identification number that the
* operating system assigns to the process.
* The range for process id values is dependent on the operating system.
* For example, an embedded system might use a 16-bit value.
* Status information about a process is retrieved from the native system
* and may change asynchronously; processes may be created or terminate
* spontaneously.
* The time between when a process terminates and the process id
* is reused for a new process is unpredictable.
* Race conditions can exist between checking the status of a process and
* acting upon it. When using ProcessHandles avoid assumptions
* about the liveness or identity of the underlying process.
* <p>
* Each ProcessHandle identifies and allows control of a process in the native
* system. ProcessHandles are returned from the factory methods {@link #current()},
* {@link #of(long)},
* {@link #children}, {@link #allChildren}, {@link #parent()} and
* {@link #allProcesses()}.
* <p>
* The {@link Process} instances created by {@link ProcessBuilder} can be queried
* for a ProcessHandle that provides information about the Process.
* ProcessHandle references should not be freely distributed.
*
* <p>
* A {@link java.util.concurrent.CompletableFuture} available from {@link #onExit}
* can be used to wait for process termination, and possibly trigger dependent
* actions.
* <p>
* The factory methods limit access to ProcessHandles using the
* SecurityManager checking the {@link RuntimePermission RuntimePermission("manageProcess")}.
* The ability to control processes is also restricted by the native system,
* ProcessHandle provides no more access to, or control over, the native process
* than would be allowed by a native application.
* <p>
* @implSpec
* In the case where ProcessHandles cannot be supported then the factory
* methods must consistently throw {@link java.lang.UnsupportedOperationException}.
* The methods of this class throw {@link java.lang.UnsupportedOperationException}
* if the operating system does not allow access to query or kill a process.
*
* @see Process
* @since 1.9
*/
public interface ProcessHandle extends Comparable<ProcessHandle> {
/**
* Returns the native process ID of the process. The native process ID is an
* identification number that the operating system assigns to the process.
*
* @return the native process ID of the process
* @throws UnsupportedOperationException if the implementation
* does not support this operation
*/
long getPid();
/**
* Returns an {@code Optional<ProcessHandle>} for an existing native process.
*
* @param pid a native process ID
* @return an {@code Optional<ProcessHandle>} of the PID for the process;
* the {@code Optional} is empty if the process does not exist
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
* @throws UnsupportedOperationException if the implementation
* does not support this operation
*/
public static Optional<ProcessHandle> of(long pid) {
return ProcessHandleImpl.get(pid);
}
/**
* Returns a ProcessHandle for the current process. The ProcessHandle cannot be
* used to destroy the current process, use {@link System#exit System.exit} instead.
*
* @return a ProcessHandle for the current process
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
* @throws UnsupportedOperationException if the implementation
* does not support this operation
*/
public static ProcessHandle current() {
return ProcessHandleImpl.current();
}
/**
* Returns an {@code Optional<ProcessHandle>} for the parent process.
* Note that Processes in a zombie state usually don't have a parent.
*
* @return an {@code Optional<ProcessHandle>} of the parent process;
* the {@code Optional} is empty if the child process does not have a parent
* or if the parent is not available, possibly due to operating system limitations
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
*/
Optional<ProcessHandle> parent();
/**
* Returns a snapshot of the current direct children of the process.
* A process that is {@link #isAlive not alive} has zero children.
* <p>
* <em>Note that processes are created and terminate asynchronously.
* There is no guarantee that a process is {@link #isAlive alive}.
* </em>
*
* @return a Stream of ProcessHandles for processes that are direct children
* of the process
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
*/
Stream<ProcessHandle> children();
/**
* Returns a snapshot of the current direct and indirect children of the process.
* A process that is {@link #isAlive not alive} has zero children.
* <p>
* <em>Note that processes are created and terminate asynchronously.
* There is no guarantee that a process is {@link #isAlive alive}.
* </em>
*
* @return a Stream of ProcessHandles for processes that are direct and
* indirect children of the process
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
*/
Stream<ProcessHandle> allChildren();
/**
* Returns a snapshot of all processes visible to the current process.
* <p>
* <em>Note that processes are created and terminate asynchronously. There
* is no guarantee that a process in the stream is alive or that no other
* processes may have been created since the inception of the snapshot.
* </em>
*
* @return a Stream of ProcessHandles for all processes
* @throws SecurityException if a security manager has been installed and
* it denies RuntimePermission("manageProcess")
* @throws UnsupportedOperationException if the implementation
* does not support this operation
*/
static Stream<ProcessHandle> allProcesses() {
return ProcessHandleImpl.children(0);
}
/**
* Returns a snapshot of information about the process.
*
* <p> An {@code Info} instance has various accessor methods that return
* information about the process, if the process is alive and the
* information is available.
*
* @return a snapshot of information about the process, always non-null
*/
Info info();
/**
* Information snapshot about the process.
* The attributes of a process vary by operating system and are not available
* in all implementations. Information about processes is limited
* by the operating system privileges of the process making the request.
* The return types are {@code Optional<T>} allowing explicit tests
* and actions if the value is available.
* @since 1.9
*/
public interface Info {
/**
* Returns the executable pathname of the process.
*
* @return an {@code Optional<String>} of the executable pathname
* of the process
*/
public Optional<String> command();
/**
* Returns an array of Strings of the arguments of the process.
*
* @return an {@code Optional<String[]>} of the arguments of the process
*/
public Optional<String[]> arguments();
/**
* Returns the start time of the process.
*
* @return an {@code Optional<Instant>} of the start time of the process
*/
public Optional<Instant> startInstant();
/**
* Returns the total cputime accumulated of the process.
*
* @return an {@code Optional<Duration>} for the accumulated total cputime
*/
public Optional<Duration> totalCpuDuration();
/**
* Return the user of the process.
*
* @return an {@code Optional<String>} for the user of the process
*/
public Optional<String> user();
}
/**
* Returns a {@code CompletableFuture<ProcessHandle>} for the termination
* of the process.
* The {@link java.util.concurrent.CompletableFuture} provides the ability
* to trigger dependent functions or actions that may be run synchronously
* or asynchronously upon process termination.
* When the process terminates the CompletableFuture is
* {@link java.util.concurrent.CompletableFuture#complete completed} regardless
* of the exit status of the process.
* The {@code onExit} method can be called multiple times to invoke
* independent actions when the process exits.
* <p>
* Calling {@code onExit().get()} waits for the process to terminate and returns
* the ProcessHandle. The future can be used to check if the process is
* {@link java.util.concurrent.CompletableFuture#isDone done} or to
* {@link java.util.concurrent.Future#get() wait} for it to terminate.
* {@link java.util.concurrent.Future#cancel(boolean) Cancelling}
* the CompleteableFuture does not affect the Process.
* <p>
* If the process is {@link #isAlive not alive} the {@link CompletableFuture}
* returned has been {@link java.util.concurrent.CompletableFuture#complete completed}.
*
* @return a new {@code CompletableFuture<ProcessHandle>} for the ProcessHandle
*
* @throws IllegalStateException if the process is the current process
*/
CompletableFuture<ProcessHandle> onExit();
/**
* Returns {@code true} if the implementation of {@link #destroy}
* normally terminates the process.
* Returns {@code false} if the implementation of {@code destroy}
* forcibly and immediately terminates the process.
*
* @return {@code true} if the implementation of {@link #destroy}
* normally terminates the process;
* otherwise, {@link #destroy} forcibly terminates the process
*/
boolean supportsNormalTermination();
/**
* Requests the process to be killed.
* Whether the process represented by this {@code ProcessHandle} object is
* {@link #supportsNormalTermination normally terminated} or not is
* implementation dependent.
* Forcible process destruction is defined as the immediate termination of the
* process, whereas normal termination allows the process to shut down cleanly.
* If the process is not alive, no action is taken.
* The operating system access controls may prevent the process
* from being killed.
* <p>
* The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is
* {@link java.util.concurrent.CompletableFuture#complete completed}
* when the process has terminated.
* <p>
* Note: The process may not terminate immediately.
* For example, {@code isAlive()} may return true for a brief period
* after {@code destroy()} is called.
*
* @return {@code true} if termination was successfully requested,
* otherwise {@code false}
* @throws IllegalStateException if the process is the current process
*/
boolean destroy();
/**
* Requests the process to be killed forcibly.
* The process represented by this {@code ProcessHandle} object is
* forcibly terminated.
* Forcible process destruction is defined as the immediate termination of the
* process, whereas normal termination allows the process to shut down cleanly.
* If the process is not alive, no action is taken.
* The operating system access controls may prevent the process
* from being killed.
* <p>
* The {@link java.util.concurrent.CompletableFuture} from {@link #onExit} is
* {@link java.util.concurrent.CompletableFuture#complete completed}
* when the process has terminated.
* <p>
* Note: The process may not terminate immediately.
* For example, {@code isAlive()} may return true for a brief period
* after {@code destroyForcibly()} is called.
*
* @return {@code true} if termination was successfully requested,
* otherwise {@code false}
* @throws IllegalStateException if the process is the current process
*/
boolean destroyForcibly();
/**
* Tests whether the process represented by this {@code ProcessHandle} is alive.
* Process termination is implementation and operating system specific.
* The process is considered alive as long as the PID is valid.
*
* @return {@code true} if the process represented by this
* {@code ProcessHandle} object has not yet terminated
*/
boolean isAlive();
/**
* Compares this ProcessHandle with the specified ProcessHandle for order.
* The order is not specified, but is consistent with {@link Object#equals},
* which returns {@code true} if and only if two instances of ProcessHandle
* are of the same implementation and represent the same system process.
* Comparison is only supported among objects of same implementation.
* If attempt is made to mutually compare two different implementations
* of {@link ProcessHandle}s, {@link ClassCastException} is thrown.
*
* @param other the ProcessHandle to be compared
* @return a negative integer, zero, or a positive integer as this object
* is less than, equal to, or greater than the specified object.
* @throws NullPointerException if the specified object is null
* @throws ClassCastException if the specified object is not of same class
* as this object
*/
@Override
int compareTo(ProcessHandle other);
}

View File

@ -0,0 +1,528 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang;
import java.security.PrivilegedAction;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import sun.misc.InnocuousThread;
import static java.security.AccessController.doPrivileged;
/**
* ProcessHandleImpl is the implementation of ProcessHandle.
*
* @see Process
* @since 1.9
*/
final class ProcessHandleImpl implements ProcessHandle {
/**
* The thread pool of "process reaper" daemon threads.
*/
private static final Executor processReaperExecutor =
doPrivileged((PrivilegedAction<Executor>) () -> {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
while (tg.getParent() != null) tg = tg.getParent();
ThreadGroup systemThreadGroup = tg;
ThreadFactory threadFactory = grimReaper -> {
// Our thread stack requirement is quite modest.
Thread t = new Thread(systemThreadGroup, grimReaper,
"process reaper", 32768);
t.setDaemon(true);
// A small attempt (probably futile) to avoid priority inversion
t.setPriority(Thread.MAX_PRIORITY);
return t;
};
return Executors.newCachedThreadPool(threadFactory);
});
private static class ExitCompletion extends CompletableFuture<Integer> {
final boolean isReaping;
ExitCompletion(boolean isReaping) {
this.isReaping = isReaping;
}
}
private static final ConcurrentMap<Long, ExitCompletion>
completions = new ConcurrentHashMap<>();
/**
* Returns a CompletableFuture that completes with process exit status when
* the process completes.
*
* @param shouldReap true if the exit value should be reaped
*/
static CompletableFuture<Integer> completion(long pid, boolean shouldReap) {
// check canonicalizing cache 1st
ExitCompletion completion = completions.get(pid);
// re-try until we get a completion that shouldReap => isReaping
while (completion == null || (shouldReap && !completion.isReaping)) {
ExitCompletion newCompletion = new ExitCompletion(shouldReap);
if (completion == null) {
completion = completions.putIfAbsent(pid, newCompletion);
} else {
completion = completions.replace(pid, completion, newCompletion)
? null : completions.get(pid);
}
if (completion == null) {
// newCompletion has just been installed successfully
completion = newCompletion;
// spawn a thread to wait for and deliver the exit value
processReaperExecutor.execute(() -> {
int exitValue = waitForProcessExit0(pid, shouldReap);
newCompletion.complete(exitValue);
// remove from cache afterwards
completions.remove(pid, newCompletion);
});
}
}
return completion;
}
@Override
public CompletableFuture<ProcessHandle> onExit() {
if (this.equals(current)) {
throw new IllegalStateException("onExit for current process not allowed");
}
return ProcessHandleImpl.completion(getPid(), false)
.handleAsync((exitStatus, unusedThrowable) -> this);
}
/**
* Wait for the process to exit, return the value.
* Conditionally reap the value if requested
* @param pid the processId
* @param reapvalue if true, the value is retrieved,
* else return the value and leave the process waitable
*
* @return the value or -1 if an error occurs
*/
private static native int waitForProcessExit0(long pid, boolean reapvalue);
/**
* Cache the ProcessHandle of this process.
*/
private static final ProcessHandleImpl current =
new ProcessHandleImpl(getCurrentPid0());
/**
* The pid of this ProcessHandle.
*/
private final long pid;
/**
* Private constructor. Instances are created by the {@code get(long)} factory.
* @param pid the pid for this instance
*/
private ProcessHandleImpl(long pid) {
this.pid = pid;
}
/**
* Returns a ProcessHandle for an existing native process.
*
* @param pid the native process identifier
* @return The ProcessHandle for the pid if the process is alive;
* or {@code null} if the process ID does not exist in the native system.
* @throws SecurityException if RuntimePermission("manageProcess") is not granted
*/
static Optional<ProcessHandle> get(long pid) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
return Optional.ofNullable(isAlive0(pid) ? new ProcessHandleImpl(pid) : null);
}
/**
* Returns a ProcessHandle corresponding known to exist pid.
* Called from ProcessImpl, it does not perform a security check or check if the process is alive.
* @param pid of the known to exist process
* @return a ProcessHandle corresponding to an existing Process instance
*/
static ProcessHandle getUnchecked(long pid) {
return new ProcessHandleImpl(pid);
}
/**
* Returns the native process ID.
* A {@code long} is used to be able to fit the system specific binary values
* for the process.
*
* @return the native process ID
*/
@Override
public long getPid() {
return pid;
}
/**
* Returns the ProcessHandle for the current native process.
*
* @return The ProcessHandle for the OS process.
* @throws SecurityException if RuntimePermission("manageProcess") is not granted
*/
public static ProcessHandleImpl current() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
return current;
}
/**
* Return the pid of the current process.
*
* @return the pid of the current process
*/
private static native long getCurrentPid0();
/**
* Returns a ProcessHandle for the parent process.
*
* @return a ProcessHandle of the parent process; {@code null} is returned
* if the child process does not have a parent
* @throws SecurityException if permission is not granted by the
* security policy
*/
static Optional<ProcessHandle> parent(long pid) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
long ppid = parent0(pid);
if (ppid <= 0) {
return Optional.empty();
}
return get(ppid);
}
/**
* Returns the parent of the native pid argument.
*
* @return the parent of the native pid; if any, otherwise -1
*/
private static native long parent0(long pid);
/**
* Returns the number of pids filled in to the array.
* @param pid if {@code pid} equals zero, then all known processes are returned;
* otherwise only direct child process pids are returned
* @param pids an allocated long array to receive the pids
* @param ppids an allocated long array to receive the parent pids; may be null
* @return if greater than or equals to zero is the number of pids in the array;
* if greater than the length of the arrays, the arrays are too small
*/
private static native int getProcessPids0(long pid, long[] pids, long[] ppids);
/**
* Destroy the process for this ProcessHandle.
* @param pid the processs ID to destroy
* @param force {@code true} if the process should be terminated forcibly;
* else {@code false} for a normal termination
*/
static void destroyProcess(long pid, boolean force) {
destroy0(pid, force);
}
private static native boolean destroy0(long pid, boolean forcibly);
@Override
public boolean destroy() {
if (this.equals(current)) {
throw new IllegalStateException("destroy of current process not allowed");
}
return destroy0(getPid(), false);
}
@Override
public boolean destroyForcibly() {
if (this.equals(current)) {
throw new IllegalStateException("destroy of current process not allowed");
}
return destroy0(getPid(), true);
}
@Override
public boolean supportsNormalTermination() {
return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
}
/**
* Tests whether the process represented by this {@code ProcessHandle} is alive.
*
* @return {@code true} if the process represented by this
* {@code ProcessHandle} object has not yet terminated.
* @since 1.9
*/
@Override
public boolean isAlive() {
return isAlive0(pid);
}
/**
* Returns true or false depending on whether the pid is alive.
* This must not reap the exitValue like the isAlive method above.
*
* @param pid the pid to check
* @return true or false
*/
private static native boolean isAlive0(long pid);
@Override
public Optional<ProcessHandle> parent() {
return parent(pid);
}
@Override
public Stream<ProcessHandle> children() {
return children(pid);
}
/**
* Returns a Stream of the children of a process or all processes.
*
* @param pid the pid of the process for which to find the children;
* 0 for all processes
* @return a stream of ProcessHandles
*/
static Stream<ProcessHandle> children(long pid) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
int size = 100;
long[] childpids = null;
while (childpids == null || size > childpids.length) {
childpids = new long[size];
size = getProcessPids0(pid, childpids, null);
}
return Arrays.stream(childpids, 0, size).mapToObj((id) -> new ProcessHandleImpl(id));
}
@Override
public Stream<ProcessHandle> allChildren() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
int size = 100;
long[] pids = null;
long[] ppids = null;
while (pids == null || size > pids.length) {
pids = new long[size];
ppids = new long[size];
size = getProcessPids0(0, pids, ppids);
}
int next = 0; // index of next process to check
int count = -1; // count of subprocesses scanned
long ppid = pid; // start looking for this parent
do {
// Scan from next to size looking for ppid
// if found, exchange it to index next
for (int i = next; i < size; i++) {
if (ppids[i] == ppid) {
swap(pids, i, next);
swap(ppids, i, next);
next++;
}
}
ppid = pids[++count]; // pick up the next pid to scan for
} while (count < next);
return Arrays.stream(pids, 0, count).mapToObj((id) -> new ProcessHandleImpl(id));
}
// Swap two elements in an array
private static void swap(long[] array, int x, int y) {
long v = array[x];
array[x] = array[y];
array[y] = v;
}
@Override
public ProcessHandle.Info info() {
return ProcessHandleImpl.Info.info(pid);
}
@Override
public int compareTo(ProcessHandle other) {
return Long.compare(pid, ((ProcessHandleImpl) other).pid);
}
@Override
public String toString() {
return Long.toString(pid);
}
@Override
public int hashCode() {
return Long.hashCode(pid);
}
@Override
public boolean equals(Object obj) {
return (obj instanceof ProcessHandleImpl) &&
(pid == ((ProcessHandleImpl) obj).pid);
}
/**
* Implementation of ProcessHandle.Info.
* Information snapshot about a process.
* The attributes of a process vary by operating system and not available
* in all implementations. Additionally, information about other processes
* is limited by the operating system privileges of the process making the request.
* If a value is not available, either a {@code null} or {@code -1} is stored.
* The accessor methods return {@code null} if the value is not available.
*/
static class Info implements ProcessHandle.Info {
static {
initIDs();
}
/**
* Initialization of JNI fieldIDs.
*/
private static native void initIDs();
/**
* Fill in this Info instance with information about the native process.
* If values are not available the native code does not modify the field.
* @param pid of the native process
*/
private native void info0(long pid);
String command;
String[] arguments;
long startTime;
long totalTime;
String user;
Info() {
command = null;
arguments = null;
startTime = -1L;
totalTime = -1L;
user = null;
}
/**
* Returns the Info object with the fields from the process.
* Whatever fields are provided by native are returned.
*
* @param pid the native process identifier
* @return ProcessHandle.Info non-null; individual fields may be null
* or -1 if not available.
*/
public static ProcessHandle.Info info(long pid) {
Info info = new Info();
info.info0(pid);
return info;
}
@Override
public Optional<String> command() {
return Optional.ofNullable(command);
}
@Override
public Optional<String[]> arguments() {
return Optional.ofNullable(arguments);
}
@Override
public Optional<Instant> startInstant() {
return (startTime > 0)
? Optional.of(Instant.ofEpochMilli(startTime))
: Optional.empty();
}
@Override
public Optional<Duration> totalCpuDuration() {
return (totalTime != -1)
? Optional.of(Duration.ofNanos(totalTime))
: Optional.empty();
}
@Override
public Optional<String> user() {
return Optional.ofNullable(user);
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(60);
sb.append('[');
if (user != null) {
sb.append("user: ");
sb.append(user());
}
if (command != null) {
if (sb.length() != 0) sb.append(", ");
sb.append("cmd: ");
sb.append(command);
}
if (arguments != null && arguments.length > 0) {
if (sb.length() != 0) sb.append(", ");
sb.append("args: ");
sb.append(Arrays.toString(arguments));
}
if (startTime != -1) {
if (sb.length() != 0) sb.append(", ");
sb.append("startTime: ");
sb.append(startInstant());
}
if (totalTime != -1) {
if (sb.length() != 0) sb.append(", ");
sb.append("totalTime: ");
sb.append(totalCpuDuration().toString());
}
sb.append(']');
return sb.toString();
}
}
}

View File

@ -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 @@ import java.util.StringTokenizer;
* "../../../technotes/guides/plugin/developer_guide/rsa_how.html#use">
* usePolicy Permission</a>.</td>
* </tr>
* <tr>
* <td>manageProcess</td>
* <td>Native process termination and information about processes
* {@link ProcessHandle}.</td>
* <td>Allows code to identify and terminate processes that it did not create.</td>
* </tr>
*
* <tr>
* <td>localeServiceProvider</td>

View File

@ -0,0 +1,371 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni.h"
#include "jni_util.h"
#include "java_lang_ProcessHandleImpl.h"
#include "java_lang_ProcessHandleImpl_Info.h"
#include <stdio.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <procfs.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <limits.h>
/**
* Implementations of ProcessHandleImpl functions that are
* NOT common to all Unix variants:
* - getProcessPids0(pid, pidArray)
*
* Implementations of ProcessHandleImpl_Info
* - totalTime, startTime
* - Command
* - Arguments
*/
/*
* Signatures for internal OS specific functions.
*/
static pid_t parentPid(JNIEnv *env, pid_t pid);
static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid);
static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid);
extern jstring uidToUser(JNIEnv* env, uid_t uid);
/* Field id for jString 'command' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_commandID;
/* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_argumentsID;
/* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_totalTimeID;
/* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_startTimeID;
/* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_userID;
/* static value for clock ticks per second. */
static long clock_ticks_per_second;
/**************************************************************
* Static method to initialize field IDs and the ticks per second rate.
*
* Class: java_lang_ProcessHandleImpl_Info
* Method: initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
(JNIEnv *env, jclass clazz) {
CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
clazz, "command", "Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
clazz, "arguments", "[Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
clazz, "totalTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
clazz, "startTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
clazz, "user", "Ljava/lang/String;"));
clock_ticks_per_second = sysconf(_SC_CLK_TCK);
}
/*
* Returns the parent pid of the requested pid.
*
* Class: java_lang_ProcessHandleImpl
* Method: parent0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
(JNIEnv *env, jobject obj, jlong jpid) {
pid_t pid = (pid_t) jpid;
pid_t ppid = -1;
if (pid == getpid()) {
ppid = getppid();
} else {
ppid = parentPid(env, pid);
}
return (jlong) ppid;
}
/*
* Returns the children of the requested pid and optionally each parent.
*
* Class: java_lang_ProcessHandleImpl
* Method: getChildPids
* Signature: (J[J)I
*
* Reads /proc and accumulates any process who parent pid matches.
* The resulting pids are stored into the array of longs.
* The number of pids is returned if they all fit.
* If the array is too short, the desired length is returned.
*/
JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
(JNIEnv *env, jclass clazz, jlong jpid,
jlongArray jarray, jlongArray jparentArray)
{
DIR* dir;
struct dirent* ptr;
pid_t pid = (pid_t) jpid;
size_t count = 0;
jlong* pids = NULL;
jlong* ppids = NULL;
size_t parentArraySize = 0;
size_t arraySize = 0;
arraySize = (*env)->GetArrayLength(env, jarray);
JNU_CHECK_EXCEPTION_RETURN(env, 0);
if (jparentArray != NULL) {
parentArraySize = (*env)->GetArrayLength(env, jparentArray);
JNU_CHECK_EXCEPTION_RETURN(env, 0);
if (arraySize != parentArraySize) {
JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
return 0;
}
}
/*
* To locate the children we scan /proc looking for files that have a
* positive integer as a filename.
*/
if ((dir = opendir("/proc")) == NULL) {
JNU_ThrowByNameWithLastError(env,
"java/lang/Runtime", "Unable to open /proc");
return 0;
}
do { // Block to break out of on Exception
pids = (*env)->GetLongArrayElements(env, jarray, NULL);
if (pids == NULL) {
break;
}
if (jparentArray != NULL) {
ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
if (ppids == NULL) {
break;
}
}
while ((ptr = readdir(dir)) != NULL) {
pid_t ppid;
/* skip files that aren't numbers */
pid_t childpid = (pid_t) atoi(ptr->d_name);
if ((int) childpid <= 0) {
continue;
}
ppid = 0;
if (pid != 0 || jparentArray != NULL) {
// parentPid opens and reads /proc/pid/stat
ppid = parentPid(env, childpid);
}
if (pid == 0 || ppid == pid) {
if (count < arraySize) {
// Only store if it fits
pids[count] = (jlong) childpid;
if (ppids != NULL) {
// Store the parentPid
ppids[count] = (jlong) ppid;
}
}
count++; // Count to tabulate size needed
}
}
} while (0);
if (pids != NULL) {
(*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
}
if (ppids != NULL) {
(*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
}
closedir(dir);
// If more pids than array had size for; count will be greater than array size
return count;
}
/*
* Returns the parent pid of a given pid, or -1 if not found
*/
static pid_t parentPid(JNIEnv *env, pid_t pid) {
FILE* fp;
pstatus_t pstatus;
int statlen;
char fn[32];
int i, p;
char* s;
/*
* Try to open /proc/%d/status
*/
snprintf(fn, sizeof fn, "/proc/%d/status", pid);
fp = fopen(fn, "r");
if (fp == NULL) {
return -1;
}
/*
* The format is: pid (command) state ppid ...
* As the command could be anything we must find the right most
* ")" and then skip the white spaces that follow it.
*/
statlen = fread(&pstatus, 1, (sizeof pstatus), fp);
fclose(fp);
if (statlen < 0) {
return -1;
}
return (pid_t) pstatus.pr_ppid;
}
/**************************************************************
* Implementation of ProcessHandleImpl_Info native methods.
*/
/*
* Fill in the Info object from the OS information about the process.
*
* Class: java_lang_ProcessHandleImpl_Info
* Method: info0
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
(JNIEnv *env, jobject jinfo, jlong jpid) {
pid_t pid = (pid_t) jpid;
getStatInfo(env, jinfo, pid);
getCmdlineInfo(env, jinfo, pid);
}
/**
* Read /proc/<pid>/stat and fill in the fields of the Info object.
* Gather the user and system times.
*/
static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
FILE* fp;
pstatus_t pstatus;
struct stat stat_buf;
int ret;
char fn[32];
int i, p;
char* s;
jlong totalTime;
/*
* Try to open /proc/%d/status
*/
snprintf(fn, sizeof fn, "/proc/%d/status", pid);
if (stat(fn, &stat_buf) < 0) {
return;
}
fp = fopen(fn, "r");
if (fp == NULL) {
return;
}
ret = fread(&pstatus, 1, (sizeof pstatus), fp);
fclose(fp);
if (ret < 0) {
return;
}
totalTime = pstatus.pr_utime.tv_sec * 1000000000L + pstatus.pr_utime.tv_nsec +
pstatus.pr_stime.tv_sec * 1000000000L + pstatus.pr_stime.tv_nsec;
(*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, totalTime);
JNU_CHECK_EXCEPTION(env);
}
static void getCmdlineInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
FILE* fp;
psinfo_t psinfo;
int ret;
char fn[32];
char exePath[PATH_MAX];
int i, p;
jlong startTime;
jobjectArray cmdArray;
jstring str = NULL;
/*
* try to open /proc/%d/psinfo
*/
snprintf(fn, sizeof fn, "/proc/%d/psinfo", pid);
fp = fopen(fn, "r");
if (fp == NULL) {
return;
}
/*
* The format is: pid (command) state ppid ...
* As the command could be anything we must find the right most
* ")" and then skip the white spaces that follow it.
*/
ret = fread(&psinfo, 1, (sizeof psinfo), fp);
fclose(fp);
if (ret < 0) {
return;
}
CHECK_NULL((str = uidToUser(env, psinfo.pr_uid)));
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, str);
JNU_CHECK_EXCEPTION(env);
startTime = (jlong)psinfo.pr_start.tv_sec * (jlong)1000 +
(jlong)psinfo.pr_start.tv_nsec / 1000000;
(*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
JNU_CHECK_EXCEPTION(env);
/*
* The path to the executable command is the link in /proc/<pid>/paths/a.out.
*/
snprintf(fn, sizeof fn, "/proc/%d/path/a.out", pid);
if ((ret = readlink(fn, exePath, PATH_MAX - 1)) < 0) {
return;
}
// null terminate and create String to store for command
exePath[ret] = '\0';
CHECK_NULL(str = JNU_NewStringPlatform(env, exePath));
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, str);
JNU_CHECK_EXCEPTION(env);
}

View File

@ -39,9 +39,7 @@ import java.util.Arrays;
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.PrivilegedActionException;
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 @@ final class ProcessImpl extends Process {
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 @@ final class ProcessImpl extends Process {
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 @@ final class ProcessImpl extends Process {
String helperPath() {
return AccessController.doPrivileged(
(PrivilegedAction<String>) () ->
helperPath(System.getProperty("java.home"),
System.getProperty("os.arch"))
(PrivilegedAction<String>) () ->
helperPath(System.getProperty("java.home"),
System.getProperty("os.arch"))
);
}
LaunchMechanism launchMechanism() {
return AccessController.doPrivileged(
(PrivilegedAction<LaunchMechanism>) () -> {
String s = System.getProperty(
"jdk.lang.Process.launchMechanism");
LaunchMechanism lm;
if (s == null) {
lm = defaultLaunchMechanism;
s = lm.name().toLowerCase(Locale.ENGLISH);
} else {
try {
lm = LaunchMechanism.valueOf(
s.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException e) {
lm = null;
}
(PrivilegedAction<LaunchMechanism>) () -> {
String s = System.getProperty(
"jdk.lang.Process.launchMechanism");
LaunchMechanism lm;
if (s == null) {
lm = defaultLaunchMechanism;
s = lm.name().toLowerCase(Locale.ENGLISH);
} else {
try {
lm = LaunchMechanism.valueOf(
s.toUpperCase(Locale.ENGLISH));
} catch (IllegalArgumentException e) {
lm = null;
}
if (lm == null || !validLaunchMechanisms.contains(lm)) {
throw new Error(
s + " is not a supported " +
"process launch mechanism on this platform."
);
}
return lm;
}
if (lm == null || !validLaunchMechanisms.contains(lm)) {
throw new Error(
s + " is not a supported " +
"process launch mechanism on this platform."
);
}
return lm;
}
);
}
static Platform get() {
String osName = AccessController.doPrivileged(
(PrivilegedAction<String>) () -> System.getProperty("os.name")
(PrivilegedAction<String>) () -> System.getProperty("os.name")
);
if (osName.equals("Linux")) { return LINUX; }
@ -173,17 +174,14 @@ final class ProcessImpl extends Process {
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 @@ final class ProcessImpl extends Process {
byte[] dir,
int[] fds,
boolean redirectErrorStream)
throws IOException;
/**
* The thread pool of "process reaper" daemon threads.
*/
private static final Executor processReaperExecutor =
doPrivileged((PrivilegedAction<Executor>) () -> {
ThreadGroup tg = Thread.currentThread().getThreadGroup();
while (tg.getParent() != null) tg = tg.getParent();
ThreadGroup systemThreadGroup = tg;
ThreadFactory threadFactory = grimReaper -> {
// Our thread stack requirement is quite modest.
Thread t = new Thread(systemThreadGroup, grimReaper,
"process reaper", 32768);
t.setDaemon(true);
// A small attempt (probably futile) to avoid priority inversion
t.setPriority(Thread.MAX_PRIORITY);
return t;
};
return Executors.newCachedThreadPool(threadFactory);
});
throws IOException;
private ProcessImpl(final byte[] prog,
final byte[] argBlock, final int argc,
@ -338,13 +313,14 @@ final class ProcessImpl extends Process {
throws IOException {
pid = forkAndExec(launchMechanism.ordinal() + 1,
helperpath,
prog,
argBlock, argc,
envBlock, envc,
dir,
fds,
redirectErrorStream);
helperpath,
prog,
argBlock, argc,
envBlock, envc,
dir,
fds,
redirectErrorStream);
processHandle = ProcessHandleImpl.getUnchecked(pid);
try {
doPrivileged((PrivilegedExceptionAction<Void>) () -> {
@ -371,18 +347,16 @@ final class ProcessImpl extends Process {
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]);
processReaperExecutor.execute(() -> {
int exitcode = waitForProcessExit(pid);
ProcessBuilder.NullInputStream.INSTANCE :
new ProcessPipeInputStream(fds[2]);
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 @@ final class ProcessImpl extends Process {
if (stdin instanceof ProcessPipeOutputStream)
((ProcessPipeOutputStream) stdin).processExited();
return null;
});
break;
@ -402,18 +378,18 @@ final class ProcessImpl extends Process {
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 @@ final class ProcessImpl extends Process {
* 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 @@ final class ProcessImpl extends Process {
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]);
processReaperExecutor.execute(() -> {
int exitcode = waitForProcessExit(pid);
ProcessBuilder.NullInputStream.INSTANCE :
new DeferredCloseProcessPipeInputStream(fds[2]);
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 @@ final class ProcessImpl extends Process {
if (stdin instanceof ProcessPipeOutputStream)
((ProcessPipeOutputStream) stdin).processExited();
return null;
});
break;
@ -492,7 +467,7 @@ final class ProcessImpl extends Process {
@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 @@ final class ProcessImpl extends Process {
return exitcode;
}
private static native void destroyProcess(int pid, boolean force);
private void destroy(boolean force) {
switch (platform) {
case LINUX:
@ -532,7 +505,7 @@ final class ProcessImpl extends Process {
// 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 @@ final class ProcessImpl extends Process {
// 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 @@ final class ProcessImpl extends Process {
}
}
@Override
public CompletableFuture<Process> onExit() {
return ProcessHandleImpl.completion(pid, false)
.handleAsync((exitStatus, unusedThrowable) -> this);
}
@Override
public ProcessHandle toHandle() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
return processHandle;
}
@Override
public boolean supportsNormalTermination() {
return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
}
@Override
public void destroy() {
destroy(false);
}
@ -629,8 +623,8 @@ final class ProcessImpl extends Process {
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 @@ final class ProcessImpl extends Process {
*
*/
private static class DeferredCloseProcessPipeInputStream
extends BufferedInputStream {
extends BufferedInputStream {
private final Object closeLock = new Object();
private int useCount = 0;

View File

@ -0,0 +1,769 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni.h"
#include "jni_util.h"
#include "java_lang_ProcessHandleImpl.h"
#include "java_lang_ProcessHandleImpl_Info.h"
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <pwd.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string.h>
#include <dirent.h>
#include <ctype.h>
/**
* Implementations of ProcessHandleImpl functions that are common to all
* Unix variants:
* - waitForProcessExit0(pid, reap)
* - getCurrentPid0()
* - destroy0(pid, force)
*/
#ifndef WIFEXITED
#define WIFEXITED(status) (((status)&0xFF) == 0)
#endif
#ifndef WEXITSTATUS
#define WEXITSTATUS(status) (((status)>>8)&0xFF)
#endif
#ifndef WIFSIGNALED
#define WIFSIGNALED(status) (((status)&0xFF) > 0 && ((status)&0xFF00) == 0)
#endif
#ifndef WTERMSIG
#define WTERMSIG(status) ((status)&0x7F)
#endif
#define RESTARTABLE(_cmd, _result) do { \
do { \
_result = _cmd; \
} while((_result == -1) && (errno == EINTR)); \
} while(0)
#define RESTARTABLE_RETURN_PTR(_cmd, _result) do { \
do { \
_result = _cmd; \
} while((_result == NULL) && (errno == EINTR)); \
} while(0)
/* Block until a child process exits and return its exit code.
* Note, can only be called once for any given pid if reapStatus = true.
*/
JNIEXPORT jint JNICALL
Java_java_lang_ProcessHandleImpl_waitForProcessExit0(JNIEnv* env,
jclass junk,
jlong jpid,
jboolean reapStatus)
{
pid_t pid = (pid_t)jpid;
errno = 0;
if (reapStatus != JNI_FALSE) {
/* Wait for the child process to exit.
* waitpid() is standard, so use it on all POSIX platforms.
* It is known to work when blocking to wait for the pid
* This returns immediately if the child has already exited.
*/
int status;
while (waitpid(pid, &status, 0) < 0) {
switch (errno) {
case ECHILD: return 0;
case EINTR: break;
default: return -1;
}
}
if (WIFEXITED(status)) {
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
/* The child exited because of a signal.
* The best value to return is 0x80 + signal number,
* because that is what all Unix shells do, and because
* it allows callers to distinguish between process exit and
* process death by signal.
* Unfortunately, the historical behavior on Solaris is to return
* the signal number, and we preserve this for compatibility. */
#ifdef __solaris__
return WTERMSIG(status);
#else
return 0x80 + WTERMSIG(status);
#endif
} else {
return status;
}
} else {
/*
* Wait for the child process to exit without reaping the exitValue.
* waitid() is standard on all POSIX platforms.
* Note: waitid on Mac OS X 10.7 seems to be broken;
* it does not return the exit status consistently.
*/
siginfo_t siginfo;
int options = WEXITED | WNOWAIT;
memset(&siginfo, 0, sizeof siginfo);
while (waitid(P_PID, pid, &siginfo, options) < 0) {
switch (errno) {
case ECHILD: return 0;
case EINTR: break;
default: return -1;
}
}
if (siginfo.si_code == CLD_EXITED) {
/*
* The child exited normally; get its exit code.
*/
return siginfo.si_status;
} else if (siginfo.si_code == CLD_KILLED || siginfo.si_code == CLD_DUMPED) {
/* The child exited because of a signal.
* The best value to return is 0x80 + signal number,
* because that is what all Unix shells do, and because
* it allows callers to distinguish between process exit and
* process death by signal.
* Unfortunately, the historical behavior on Solaris is to return
* the signal number, and we preserve this for compatibility. */
#ifdef __solaris__
return WTERMSIG(siginfo.si_status);
#else
return 0x80 + WTERMSIG(siginfo.si_status);
#endif
} else {
/*
* Unknown exit code; pass it through.
*/
return siginfo.si_status;
}
}
}
/*
* Class: java_lang_ProcessHandleImpl
* Method: getCurrentPid0
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_getCurrentPid0
(JNIEnv *env, jclass clazz) {
pid_t pid = getpid();
return (jlong) pid;
}
/*
* Class: java_lang_ProcessHandleImpl
* Method: isAlive0
* Signature: (J)Z
*/
JNIEXPORT jboolean JNICALL Java_java_lang_ProcessHandleImpl_isAlive0
(JNIEnv *env, jobject obj, jlong jpid) {
pid_t pid = (pid_t) jpid;
return (kill(pid, 0) < 0) ? JNI_FALSE : JNI_TRUE;
}
/*
* Class: java_lang_ProcessHandleImpl
* Method: destroy0
* Signature: (Z)Z
*/
JNIEXPORT jboolean JNICALL Java_java_lang_ProcessHandleImpl_destroy0
(JNIEnv *env, jobject obj, jlong jpid, jboolean force) {
pid_t pid = (pid_t) jpid;
int sig = (force == JNI_TRUE) ? SIGKILL : SIGTERM;
return (kill(pid, sig) >= 0);
}
/**
* Size of password or group entry when not available via sysconf
*/
#define ENT_BUF_SIZE 1024
/**
* Return a strong username for the uid_t or null.
*/
jstring uidToUser(JNIEnv* env, uid_t uid) {
int result = 0;
int buflen;
char* pwbuf;
jstring name = NULL;
/* allocate buffer for password record */
buflen = (int)sysconf(_SC_GETPW_R_SIZE_MAX);
if (buflen == -1)
buflen = ENT_BUF_SIZE;
pwbuf = (char*)malloc(buflen);
if (pwbuf == NULL) {
JNU_ThrowOutOfMemoryError(env, "Unable to open getpwent");
} else {
struct passwd pwent;
struct passwd* p = NULL;
#ifdef __solaris__
RESTARTABLE_RETURN_PTR(getpwuid_r(uid, &pwent, pwbuf, (size_t)buflen), p);
#else
RESTARTABLE(getpwuid_r(uid, &pwent, pwbuf, (size_t)buflen, &p), result);
#endif
// Return the Java String if a name was found
if (result == 0 && p != NULL &&
p->pw_name != NULL && *(p->pw_name) != '\0') {
name = JNU_NewStringPlatform(env, p->pw_name);
}
free(pwbuf);
}
return name;
}
/**
* Implementations of ProcessHandleImpl functions that are common to
* (some) Unix variants:
* - getProcessPids0(pid, pidArray, parentArray)
*/
#if defined(__linux__) || defined(__AIX__)
/*
* Signatures for internal OS specific functions.
*/
static pid_t parentPid(JNIEnv *env, pid_t pid);
static jint getChildren(JNIEnv *env, jlong jpid,
jlongArray array, jlongArray jparentArray);
static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid);
static void getCmdlineInfo(JNIEnv *env, pid_t pid, jobject jinfo);
static long long getBoottime(JNIEnv *env);
jstring uidToUser(JNIEnv* env, uid_t uid);
/* Field id for jString 'command' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_commandID;
/* Field id for jString[] 'arguments' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_argumentsID;
/* Field id for jlong 'totalTime' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_totalTimeID;
/* Field id for jlong 'startTime' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_startTimeID;
/* Field id for jString 'user' in java.lang.ProcessHandleImpl.Info */
static jfieldID ProcessHandleImpl_Info_userID;
/* static value for clock ticks per second. */
static long clock_ticks_per_second;
/* A static offset in milliseconds since boot. */
static long long bootTime_ms;
/**************************************************************
* Static method to initialize field IDs and the ticks per second rate.
*
* Class: java_lang_ProcessHandleImpl_Info
* Method: initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
(JNIEnv *env, jclass clazz) {
CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
clazz, "command", "Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
clazz, "arguments", "[Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
clazz, "totalTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
clazz, "startTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
clazz, "user", "Ljava/lang/String;"));
clock_ticks_per_second = sysconf(_SC_CLK_TCK);
bootTime_ms = getBoottime(env);
}
/*
* Returns the parent pid of the requested pid.
*
* Class: java_lang_ProcessHandleImpl
* Method: parent0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
(JNIEnv *env, jobject obj, jlong jpid) {
pid_t pid = (pid_t) jpid;
pid_t ppid = -1;
pid_t mypid = getpid();
if (pid == mypid) {
ppid = getppid();
} else {
ppid = parentPid(env, pid);
}
return (jlong) ppid;
}
/*
* Returns the children of the requested pid and optionally each parent.
*
* Class: java_lang_ProcessHandleImpl
* Method: getChildPids
* Signature: (J[J[J)I
*/
JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
(JNIEnv *env, jclass clazz, jlong jpid,
jlongArray jarray, jlongArray jparentArray) {
return getChildren(env, jpid, jarray, jparentArray);
}
/*
* Reads /proc and accumulates any process who parent pid matches.
* The resulting pids are stored into the array of longs.
* The number of pids is returned if they all fit.
* If the array is too short, the negative of the desired length is returned.
*/
static jint getChildren(JNIEnv *env, jlong jpid,
jlongArray jarray, jlongArray jparentArray) {
DIR* dir;
struct dirent* ptr;
pid_t pid = (pid_t) jpid;
pid_t ppid = 0;
size_t count = 0;
jlong* pids = NULL;
jlong* ppids = NULL;
size_t parentArraySize = 0;
size_t arraySize = 0;
arraySize = (*env)->GetArrayLength(env, jarray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (jparentArray != NULL) {
parentArraySize = (*env)->GetArrayLength(env, jparentArray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (arraySize != parentArraySize) {
JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
return 0;
}
}
/*
* To locate the children we scan /proc looking for files that have a
* position integer as a filename.
*/
if ((dir = opendir("/proc")) == NULL) {
JNU_ThrowByNameWithLastError(env,
"java/lang/Runtime", "Unable to open /proc");
return -1;
}
do { // Block to break out of on Exception
pids = (*env)->GetLongArrayElements(env, jarray, NULL);
if (pids == NULL) {
break;
}
if (jparentArray != NULL) {
ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
if (ppids == NULL) {
break;
}
}
while ((ptr = readdir(dir)) != NULL) {
/* skip files that aren't numbers */
pid_t childpid = (pid_t) atoi(ptr->d_name);
if ((int) childpid <= 0) {
continue;
}
ppid = 0;
if (pid != 0 || jparentArray != NULL) {
// parentPid opens and reads /proc/pid/stat
ppid = parentPid(env, childpid);
}
if (pid == 0 || ppid == pid) {
if (count < arraySize) {
// Only store if it fits
pids[count] = (jlong) childpid;
if (ppids != NULL) {
// Store the parentPid
ppids[count] = (jlong) ppid;
}
}
count++; // Count to tabulate size needed
}
}
} while (0);
if (pids != NULL) {
(*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
}
if (ppids != NULL) {
(*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
}
closedir(dir);
// If more pids than array had size for; count will be greater than array size
return count;
}
/*
* Returns the parent pid of a given pid, or -1 if not found
*/
static pid_t parentPid(JNIEnv *env, pid_t pid) {
char state;
FILE* fp;
char stat[2048];
int statlen;
char fn[32];
int i, p;
char* s;
/*
* try to open /proc/%d/stat
*/
snprintf(fn, sizeof fn, "/proc/%d/stat", pid);
fp = fopen(fn, "r");
if (fp == NULL) {
return -1;
}
/*
* The format is: pid (command) state ppid ...
* As the command could be anything we must find the right most
* ")" and then skip the white spaces that follow it.
*/
statlen = fread(stat, 1, (sizeof stat - 1), fp);
fclose(fp);
if (statlen < 0) {
return -1;
}
stat[statlen] = '\0';
s = strrchr(stat, ')');
if (s == NULL) {
return -1;
}
do s++; while (isspace(*s));
i = sscanf(s, "%c %d", &state, &p);
if (i != 2) {
return (pid_t)-1;
}
return (pid_t) p;
}
/**************************************************************
* Implementation of ProcessHandleImpl_Info native methods.
*/
/*
* Fill in the Info object from the OS information about the process.
*
* Class: java_lang_ProcessHandleImpl_Info
* Method: info0
* Signature: (JLjava/lang/ProcessHandle/Info;)I
*/
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
(JNIEnv *env, jobject jinfo, jlong jpid) {
pid_t pid = (pid_t) jpid;
getStatInfo(env, jinfo, (pid_t)pid);
getCmdlineInfo(env, pid, jinfo);
}
/**
* Read /proc/<pid>/stat and fill in the fields of the Info object.
* The executable name, plus the user, system, and start times are gathered.
*/
static void getStatInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
char state;
FILE* fp;
char buffer[2048];
struct stat stat_buf;
int statlen;
char fn[32];
int i, ppid = -2;
char* s;
char *cmd;
jstring name = NULL;
unsigned long userTime = 0; // clock tics
unsigned long totalTime = 0; // clock tics
jlong total = 0; // nano seconds
unsigned long long startTime = 0; // microseconds
/*
* Try to stat and then open /proc/%d/stat
*/
snprintf(fn, sizeof fn, "/proc/%d/stat", pid);
if (stat(fn, &stat_buf) < 0) {
return;
}
CHECK_NULL((name = uidToUser(env, stat_buf.st_uid)));
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, name);
JNU_CHECK_EXCEPTION(env);
fp = fopen(fn, "r");
if (fp == NULL) {
return;
}
/*
* The format is: pid (command) state ppid ...
* As the command could be anything we must find the right most
* ")" and then skip the white spaces that follow it.
*/
statlen = fread(buffer, 1, (sizeof buffer - 1), fp);
fclose(fp);
if (statlen < 0) {
return;
}
buffer[statlen] = '\0';
s = strchr(buffer, '(');
if (s == NULL) {
return;
}
// Found start of command, skip to end
s++;
s = strrchr(s, ')');
if (s == NULL) {
return;
}
s++;
// Scan the needed fields from status, retaining only ppid(4),
// utime (14), stime(15), starttime(22)
i = sscanf(s, " %c %d %*d %*d %*d %*d %*d %*u %*u %*u %*u %lu %lu %*d %*d %*d %*d %*d %*d %llu",
&state, &ppid, &userTime, &totalTime, &startTime);
if (i != 5) {
return; // not all values parsed; return error
}
total = (userTime + totalTime) * (jlong)(1000000000 / clock_ticks_per_second);
startTime = bootTime_ms + ((startTime * 1000) / clock_ticks_per_second);
(*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_totalTimeID, total);
JNU_CHECK_EXCEPTION(env);
(*env)->SetLongField(env, jinfo, ProcessHandleImpl_Info_startTimeID, startTime);
JNU_CHECK_EXCEPTION(env);
}
/**
* Construct the argument array by parsing the arguments from the sequence
* of arguments. The zero'th arg is the command executable
*/
static int fillArgArray(JNIEnv *env, jobject jinfo,
int nargs, char *cp, char *argsEnd, jstring cmdexe) {
jobject argsArray;
int i;
if (nargs < 1) {
return 0;
}
if (cmdexe == NULL) {
// Create a string from arg[0]
CHECK_NULL_RETURN((cmdexe = JNU_NewStringPlatform(env, cp)), -1);
}
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_commandID, cmdexe);
JNU_CHECK_EXCEPTION_RETURN(env, -3);
// Create a String array for nargs-1 elements
argsArray = (*env)->NewObjectArray(env, nargs - 1, JNU_ClassString(env), NULL);
CHECK_NULL_RETURN(argsArray, -1);
for (i = 0; i < nargs - 1; i++) {
jstring str = NULL;
cp += strnlen(cp, (argsEnd - cp)) + 1;
if (cp > argsEnd || *cp == '\0') {
return -2; // Off the end pointer or an empty argument is an error
}
CHECK_NULL_RETURN((str = JNU_NewStringPlatform(env, cp)), -1);
(*env)->SetObjectArrayElement(env, argsArray, i, str);
JNU_CHECK_EXCEPTION_RETURN(env, -3);
}
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_argumentsID, argsArray);
JNU_CHECK_EXCEPTION_RETURN(env, -4);
return 0;
}
static void getCmdlineInfo(JNIEnv *env, pid_t pid, jobject jinfo) {
int fd;
int cmdlen = 0;
char *cmdline = NULL, *cmdEnd; // used for command line args and exe
jstring cmdexe = NULL;
char fn[32];
/*
* Try to open /proc/%d/cmdline
*/
snprintf(fn, sizeof fn, "/proc/%d/cmdline", pid);
if ((fd = open(fn, O_RDONLY)) < 0) {
return;
}
do { // Block to break out of on errors
int i;
char *s;
cmdline = (char*)malloc(PATH_MAX);
if (cmdline == NULL) {
break;
}
/*
* The path to the executable command is the link in /proc/<pid>/exe.
*/
snprintf(fn, sizeof fn, "/proc/%d/exe", pid);
if ((cmdlen = readlink(fn, cmdline, PATH_MAX - 1)) > 0) {
// null terminate and create String to store for command
cmdline[cmdlen] = '\0';
cmdexe = JNU_NewStringPlatform(env, cmdline);
(*env)->ExceptionClear(env); // unconditionally clear any exception
}
/*
* The buffer format is the arguments nul terminated with an extra nul.
*/
cmdlen = read(fd, cmdline, PATH_MAX-1);
if (cmdlen < 0) {
break;
}
// Terminate the buffer and count the arguments
cmdline[cmdlen] = '\0';
cmdEnd = &cmdline[cmdlen + 1];
for (s = cmdline,i = 0; *s != '\0' && (s < cmdEnd); i++) {
s += strnlen(s, (cmdEnd - s)) + 1;
}
if (fillArgArray(env, jinfo, i, cmdline, cmdEnd, cmdexe) < 0) {
break;
}
} while (0);
if (cmdline != NULL) {
free(cmdline);
}
if (fd >= 0) {
close(fd);
}
}
/**
* Read the boottime from /proc/stat.
*/
static long long getBoottime(JNIEnv *env) {
FILE *fp;
char *line = NULL;
size_t len = 0;
long long bootTime = 0;
fp = fopen("/proc/stat", "r");
if (fp == NULL) {
return -1;
}
while (getline(&line, &len, fp) != -1) {
if (sscanf(line, "btime %llu", &bootTime) == 1) {
break;
}
}
free(line);
if (fp != 0) {
fclose(fp);
}
return bootTime * 1000;
}
#endif // defined(__linux__) || defined(__AIX__)
/* Block until a child process exits and return its exit code.
Note, can only be called once for any given pid. */
JNIEXPORT jint JNICALL
Java_java_lang_ProcessImpl_waitForProcessExit(JNIEnv* env,
jobject junk,
jint pid)
{
/* We used to use waitid() on Solaris, waitpid() on Linux, but
* waitpid() is more standard, so use it on all POSIX platforms. */
int status;
/* Wait for the child process to exit. This returns immediately if
the child has already exited. */
while (waitpid(pid, &status, 0) < 0) {
switch (errno) {
case ECHILD: return 0;
case EINTR: break;
default: return -1;
}
}
if (WIFEXITED(status)) {
/*
* The child exited normally; get its exit code.
*/
return WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
/* The child exited because of a signal.
* The best value to return is 0x80 + signal number,
* because that is what all Unix shells do, and because
* it allows callers to distinguish between process exit and
* process death by signal.
* Unfortunately, the historical behavior on Solaris is to return
* the signal number, and we preserve this for compatibility. */
#ifdef __solaris__
return WTERMSIG(status);
#else
return 0x80 + WTERMSIG(status);
#endif
} else {
/*
* Unknown exit code; pass it through.
*/
return status;
}
}

View File

@ -226,52 +226,6 @@ Java_java_lang_ProcessImpl_init(JNIEnv *env, jclass clazz)
#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 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env,
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);
}

View File

@ -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.FileOutputStream;
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 @@ final class ProcessImpl extends Process {
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 @@ final class ProcessImpl extends Process {
}
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 @@ final class ProcessImpl extends Process {
handle = create(cmdstr, envblock, path,
stdHandles, redirectErrorStream);
processHandle = ProcessHandleImpl.getUnchecked(getProcessId0(handle));
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
@ -481,7 +488,30 @@ final class ProcessImpl extends Process {
private static native void waitForTimeoutInterruptibly(
long handle, long timeout);
public void destroy() { terminateProcess(handle); }
@Override
public void destroy() {
terminateProcess(handle);
}
@Override
public CompletableFuture<Process> onExit() {
return ProcessHandleImpl.completion(getPid(), false)
.handleAsync((exitStatus, unusedThrowable) -> this);
}
@Override
public ProcessHandle toHandle() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
return processHandle;
}
@Override
public boolean supportsNormalTermination() {
return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
}
@Override
public Process destroyForcibly() {
@ -493,8 +523,7 @@ final class ProcessImpl extends Process {
@Override
public long getPid() {
int pid = getProcessId0(handle);
return pid;
return processHandle.getPid();
}
private static native int getProcessId0(long handle);
@ -538,7 +567,7 @@ final class ProcessImpl extends Process {
* 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)

View File

@ -0,0 +1,426 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include "jni.h"
#include "jvm.h"
#include "jni_util.h"
#include "java_lang_ProcessHandleImpl.h"
#include "java_lang_ProcessHandleImpl_Info.h"
#include <windows.h>
#include <tlhelp32.h>
static void getStatInfo(JNIEnv *env, HANDLE handle, jobject jinfo);
static void getCmdlineInfo(JNIEnv *env, HANDLE handle, jobject jinfo);
static void procToUser( JNIEnv *env, HANDLE handle, jobject jinfo);
/**************************************************************
* Implementation of ProcessHandleImpl_Info native methods.
*/
/* Field id for jString 'command' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_commandID;
/* Field id for jString[] 'arguments' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_argumentsID;
/* Field id for jlong 'totalTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_totalTimeID;
/* Field id for jlong 'startTime' in java.lang.ProcessHandle.Info */
static jfieldID ProcessHandleImpl_Info_startTimeID;
/* Field id for jString 'accountName' in java.lang.ProcessHandleImpl.UserPrincipal */
static jfieldID ProcessHandleImpl_Info_userID;
/**************************************************************
* Static method to initialize field IDs.
*
* Class: java_lang_ProcessHandleImpl_Info
* Method: initIDs
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_initIDs
(JNIEnv *env, jclass clazz) {
CHECK_NULL(ProcessHandleImpl_Info_commandID = (*env)->GetFieldID(env,
clazz, "command", "Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_argumentsID = (*env)->GetFieldID(env,
clazz, "arguments", "[Ljava/lang/String;"));
CHECK_NULL(ProcessHandleImpl_Info_totalTimeID = (*env)->GetFieldID(env,
clazz, "totalTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_startTimeID = (*env)->GetFieldID(env,
clazz, "startTime", "J"));
CHECK_NULL(ProcessHandleImpl_Info_userID = (*env)->GetFieldID(env,
clazz, "user", "Ljava/lang/String;"));
}
/*
* Block until a child process exits and return its exit code.
*/
JNIEXPORT jint JNICALL
Java_java_lang_ProcessHandleImpl_waitForProcessExit0(JNIEnv* env,
jclass junk,
jlong jpid,
jboolean reapStatus) {
DWORD pid = (DWORD)jpid;
DWORD exitValue = -1;
HANDLE handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, pid);
if (handle == NULL) {
return exitValue; // No process with that pid is alive
}
do {
if (!GetExitCodeProcess(handle, &exitValue)) {
JNU_ThrowByNameWithLastError(env,
"java/lang/Runtime", "GetExitCodeProcess");
break;
}
if (exitValue == STILL_ACTIVE) {
HANDLE events[2];
events[0] = handle;
events[1] = JVM_GetThreadInterruptEvent();
if (WaitForMultipleObjects(sizeof(events)/sizeof(events[0]), events,
FALSE, /* Wait for ANY event */
INFINITE) /* Wait forever */
== WAIT_FAILED) {
JNU_ThrowByNameWithLastError(env,
"java/lang/Runtime", "WaitForMultipleObjects");
break;
}
}
} while (exitValue == STILL_ACTIVE);
CloseHandle(handle); // Ignore return code
return exitValue;
}
/*
* Returns the pid of the caller.
*
* Class: java_lang_ProcessHandleImpl
* Method: getCurrentPid0
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_getCurrentPid0
(JNIEnv *env, jclass clazz) {
DWORD pid = GetCurrentProcessId();
return (jlong)pid;
}
/*
* Returns the parent pid of the requested pid.
*
* Class: java_lang_ProcessHandleImpl
* Method: parent0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_java_lang_ProcessHandleImpl_parent0
(JNIEnv *env, jclass clazz, jlong jpid) {
DWORD ppid = -1;
DWORD wpid = (DWORD)jpid;
PROCESSENTRY32 pe32;
HANDLE hProcessSnap;
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
JNU_ThrowByName(env,
"java/lang/RuntimeException", "snapshot not available");
return -1;
}
// Retrieve information about the first process,
pe32.dwSize = sizeof (PROCESSENTRY32);
if (Process32First(hProcessSnap, &pe32)) {
// Now walk the snapshot of processes, and
do {
if (wpid == pe32.th32ProcessID) {
ppid = pe32.th32ParentProcessID;
break;
}
} while (Process32Next(hProcessSnap, &pe32));
} else {
JNU_ThrowByName(env,
"java/lang/RuntimeException", "snapshot not available");
return -1;
}
CloseHandle(hProcessSnap); // Ignore return code
return (jlong)ppid;
}
/*
* Returns the children of the requested pid and optionally each parent.
*
* Class: java_lang_ProcessHandleImpl
* Method: getChildPids
* Signature: (J[J[J)I
*/
JNIEXPORT jint JNICALL Java_java_lang_ProcessHandleImpl_getProcessPids0
(JNIEnv *env, jclass clazz, jlong jpid,
jlongArray jarray, jlongArray jparentArray) {
HANDLE hProcessSnap;
PROCESSENTRY32 pe32;
DWORD ppid = (DWORD)jpid;
size_t count = 0;
jlong* pids = NULL;
jlong* ppids = NULL;
size_t parentArraySize = 0;
size_t arraySize = 0;
arraySize = (*env)->GetArrayLength(env, jarray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (jparentArray != NULL) {
parentArraySize = (*env)->GetArrayLength(env, jparentArray);
JNU_CHECK_EXCEPTION_RETURN(env, -1);
if (arraySize != parentArraySize) {
JNU_ThrowIllegalArgumentException(env, "array sizes not equal");
return 0;
}
}
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE) {
JNU_ThrowByName(env,
"java/lang/RuntimeException", "snapshot not available");
return 0;
}
// Retrieve information about the first process,
pe32.dwSize = sizeof (PROCESSENTRY32);
if (Process32First(hProcessSnap, &pe32)) {
do { // Block to break out of on Exception
pids = (*env)->GetLongArrayElements(env, jarray, NULL);
if (pids == NULL) {
break;
}
if (jparentArray != NULL) {
ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL);
if (ppids == NULL) {
break;
}
}
// Now walk the snapshot of processes, and
// save information about each process in turn
do {
if (ppid == 0 ||
(pe32.th32ParentProcessID > 0
&& (pe32.th32ParentProcessID == ppid))) {
if (count < arraySize) {
// Only store if it fits
pids[count] = (jlong)pe32.th32ProcessID;
if (ppids != NULL) {
// Store the parentPid
ppids[count] = (jlong) pe32.th32ParentProcessID;
}
}
count++; // Count to tabulate size needed
}
} while (Process32Next(hProcessSnap, &pe32));
} while (0);
if (pids != NULL) {
(*env)->ReleaseLongArrayElements(env, jarray, pids, 0);
}
if (ppids != NULL) {
(*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0);
}
} else {
JNU_ThrowByName(env,
"java/lang/RuntimeException", "snapshot not available");
return 0;
}
CloseHandle(hProcessSnap);
// If more pids than array had size for; count will be greater than array size
return (jint)count;
}
/*
* Destroy the process.
*
* Class: java_lang_ProcessHandleImpl
* Method: destroy0
* Signature: (Z)V
*/
JNIEXPORT jboolean JNICALL Java_java_lang_ProcessHandleImpl_destroy0
(JNIEnv *env, jclass clazz, jlong jpid, jboolean force) {
DWORD pid = (DWORD)jpid;
HANDLE handle = OpenProcess(PROCESS_TERMINATE, FALSE, pid);
if (handle != NULL) {
TerminateProcess(handle, 1);
CloseHandle(handle); // Ignore return code
return JNI_TRUE;
}
return JNI_FALSE;
}
/*
* Class: java_lang_ProcessHandleImpl
* Method: isAlive0
* Signature: (J)Z
*/
JNIEXPORT jboolean JNICALL Java_java_lang_ProcessHandleImpl_isAlive0
(JNIEnv *env, jclass clazz, jlong jpid) {
DWORD pid = (DWORD)jpid;
jboolean ret = JNI_FALSE;
HANDLE handle =
OpenProcess(THREAD_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, pid);
if (handle != NULL) {
DWORD dwExitStatus;
GetExitCodeProcess(handle, &dwExitStatus);
CloseHandle(handle); // Ignore return code
ret = (dwExitStatus == STILL_ACTIVE);
}
return ret;
}
/**
* Assemble a 64 bit value from two 32 bit values.
*/
static jlong jlong_from(jint high, jint low) {
jlong result = 0;
result = ((jlong)high << 32) | ((0x000000000ffffffff) & (jlong)low);
return result;
}
/*
* Fill in the Info object from the OS information about the process.
*
* Class: java_lang_ProcessHandleImpl
* Method: info0
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_java_lang_ProcessHandleImpl_00024Info_info0
(JNIEnv *env, jobject jinfo, jlong jpid) {
DWORD pid = (DWORD)jpid;
int ret = 0;
HANDLE handle =
OpenProcess(THREAD_QUERY_INFORMATION | PROCESS_QUERY_LIMITED_INFORMATION,
FALSE, pid);
if (handle == NULL) {
return;
}
getStatInfo(env, handle, jinfo);
getCmdlineInfo(env, handle, jinfo);
procToUser(env, handle, jinfo);
CloseHandle(handle); // Ignore return code
}
/**
* Read /proc/<pid>/stat and fill in the fields of the Info object.
* The executable name, plus the user, system, and start times are gathered.
*/
static void getStatInfo(JNIEnv *env, HANDLE handle, jobject jinfo) {
FILETIME CreationTime;
FILETIME ExitTime;
FILETIME KernelTime;
FILETIME UserTime;
jlong userTime; // nanoseconds
jlong totalTime; // nanoseconds
jlong startTime; // nanoseconds
UserTime.dwHighDateTime = 0;
UserTime.dwLowDateTime = 0;
KernelTime.dwHighDateTime = 0;
KernelTime.dwLowDateTime = 0;
CreationTime.dwHighDateTime = 0;
CreationTime.dwLowDateTime = 0;
if (GetProcessTimes(handle, &CreationTime, &ExitTime, &KernelTime, &UserTime)) {
userTime = jlong_from(UserTime.dwHighDateTime, UserTime.dwLowDateTime);
totalTime = jlong_from( KernelTime.dwHighDateTime, KernelTime.dwLowDateTime);
totalTime = (totalTime + userTime) * 100; // convert sum to nano-seconds
startTime = jlong_from(CreationTime.dwHighDateTime,
CreationTime.dwLowDateTime) / 10000;
startTime -= 11644473600000L; // Rebase Epoch from 1601 to 1970
(*env)->SetLongField(env, jinfo,
ProcessHandleImpl_Info_totalTimeID, totalTime);
JNU_CHECK_EXCEPTION(env);
(*env)->SetLongField(env, jinfo,
ProcessHandleImpl_Info_startTimeID, startTime);
JNU_CHECK_EXCEPTION(env);
}
}
static void getCmdlineInfo(JNIEnv *env, HANDLE handle, jobject jinfo) {
char exeName[1024];
int bufsize = sizeof exeName;
jstring commandObj;
if (QueryFullProcessImageName(handle, 0, exeName, &bufsize)) {
commandObj = (*env)->NewStringUTF(env, exeName);
CHECK_NULL(commandObj);
(*env)->SetObjectField(env, jinfo,
ProcessHandleImpl_Info_commandID, commandObj);
}
}
static void procToUser( JNIEnv *env, HANDLE handle, jobject jinfo) {
#define TOKEN_LEN 256
DWORD token_len = TOKEN_LEN;
char token_buf[TOKEN_LEN];
TOKEN_USER *token_user = (TOKEN_USER*)token_buf;
HANDLE tokenHandle;
WCHAR domain[255];
WCHAR name[255];
DWORD domainLen = sizeof(domain);
DWORD nameLen = sizeof(name);
SID_NAME_USE use;
jstring s;
int ret;
if (!OpenProcessToken(handle, TOKEN_READ, &tokenHandle)) {
return;
}
ret = GetTokenInformation(tokenHandle, TokenUser, token_user,
token_len, &token_len);
CloseHandle(tokenHandle); // always close handle
if (!ret) {
JNU_ThrowByNameWithLastError(env,
"java/lang/RuntimeException", "GetTokenInformation");
return;
}
if (LookupAccountSidW(NULL, token_user->User.Sid, &name[0], &nameLen,
&domain[0], &domainLen, &use) == 0) {
// Name not available
return;
}
s = (*env)->NewString(env, (const jchar *)name, (jsize)wcslen(name));
CHECK_NULL(s);
(*env)->SetObjectField(env, jinfo, ProcessHandleImpl_Info_userID, s);
}

View File

@ -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

View File

@ -1175,13 +1175,13 @@ public class Basic {
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 @@ public class Basic {
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);}

View File

@ -0,0 +1,143 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;
import java.io.IOException;
import java.util.Optional;
import java.util.List;
import java.util.stream.Collectors;
import org.testng.TestNG;
import org.testng.annotations.Test;
/*
* @test
* @summary Basic tests for ProcessHandler
* @author Roger Riggs
*/
public class Basic {
/**
* Tests of ProcessHandle.current.
*/
@Test
public static void test1() {
try {
ProcessHandle self = ProcessHandle.current();
ProcessHandle self1 = ProcessHandle.current();
assertEquals(self, self1); //, "get pid twice should be same %d: %d");
} finally {
// Cleanup any left over processes
ProcessHandle.current().children().forEach(ProcessHandle::destroy);
}
}
/**
* Tests of ProcessHandle.get.
*/
@Test
public static void test2() {
try {
ProcessHandle self = ProcessHandle.current();
long pid = self.getPid(); // known native process id
Optional<ProcessHandle> self1 = ProcessHandle.of(pid);
assertEquals(self1.get(), self,
"ProcessHandle.of(x.getPid()) should be equal getPid() %d: %d");
Optional<ProcessHandle> ph = ProcessHandle.of(pid);
assertEquals(pid, ph.get().getPid());
} finally {
// Cleanup any left over processes
ProcessHandle.current().children().forEach(ProcessHandle::destroy);
}
}
@Test
public static void test3() {
// Test can get parent of current
ProcessHandle ph = ProcessHandle.current();
try {
Optional<ProcessHandle> pph = ph.parent();
assertTrue(pph.isPresent(), "Current has a Parent");
} finally {
// Cleanup any left over processes
ProcessHandle.current().children().forEach(ProcessHandle::destroy);
}
}
@Test
public static void test4() {
try {
Process p = new ProcessBuilder("sleep", "0").start();
p.waitFor();
long deadPid = p.getPid();
p = null; // Forget the process
Optional<ProcessHandle> t = ProcessHandle.of(deadPid);
assertFalse(t.isPresent(), "Handle created for invalid pid:" + t);
} catch (IOException | InterruptedException ex) {
fail("Unexpected exception", ex);
} finally {
// Cleanup any left over processes
ProcessHandle.current().children().forEach(ProcessHandle::destroy);
}
}
@Test
public static void test5() {
// Always contains itself.
ProcessHandle current = ProcessHandle.current();
List<ProcessHandle> list = ProcessHandle.allProcesses().collect(Collectors.toList());
if (!list.stream()
.anyMatch(ph -> ph.equals(ProcessHandle.current()))) {
System.out.printf("current: %s%n", current);
System.out.printf("all processes.size: %d%n", list.size());
list.forEach(p -> ProcessUtil.printProcess(p, " allProcesses: "));
fail("current process not found in all processes");
}
}
@Test(expectedExceptions = IllegalStateException.class)
public static void test6() {
ProcessHandle.current().onExit();
}
@Test(expectedExceptions = IllegalStateException.class)
public static void test7() {
ProcessHandle.current().destroyForcibly();
}
// Main can be used to run the tests from the command line with only testng.jar.
@SuppressWarnings("raw_types")
public static void main(String[] args) {
Class<?>[] testclass = {TreeTest.class};
TestNG testng = new TestNG();
testng.setTestClasses(testclass);
testng.run();
}
}

View File

@ -0,0 +1,350 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.File;
import java.io.BufferedReader;
import java.io.IOException;
import java.lang.ProcessBuilder;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.Scanner;
import java.util.StringTokenizer;
import java.util.concurrent.TimeUnit;
import jdk.testlibrary.Platform;
import org.testng.Assert;
import org.testng.annotations.Test;
import org.testng.TestNG;
/*
* @test
* @library /lib/testlibrary
* @summary Functions of ProcessHandle.Info
* @author Roger Riggs
*/
public class InfoTest {
static String whoami;
static {
ProcessBuilder pb = new ProcessBuilder("whoami");
String fullName;
try {
fullName = new Scanner(pb.start().getInputStream()).nextLine();
StringTokenizer st = new StringTokenizer(fullName, "\\");
while (st.hasMoreTokens()) {
whoami = st.nextToken();
}
System.out.printf("whoami: %s, user.name: %s%n", whoami, System.getProperty("user.name"));
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
// Main can be used to run the tests from the command line with only testng.jar.
@SuppressWarnings("raw_types")
public static void main(String[] args) {
Class<?>[] testclass = {InfoTest.class};
TestNG testng = new TestNG();
testng.setTestClasses(testclass);
testng.run();
}
/**
* Test that cputime used shows up in ProcessHandle.info
*/
@Test
public static void test1() {
System.out.println("Note: when run in samevm mode the cputime of the " +
"test runner is included.");
ProcessHandle self = ProcessHandle.current();
Duration somecpu = Duration.ofMillis(200L);
Instant end = Instant.now().plus(somecpu);
while (Instant.now().isBefore(end)) {
// waste the cpu
}
ProcessHandle.Info info = self.info();
System.out.printf(" info: %s%n", info);
Optional<Duration> totalCpu = info.totalCpuDuration();
if (totalCpu.isPresent() && (totalCpu.get().compareTo(somecpu) < 0)) {
Assert.fail("reported cputime less than expected: " + somecpu + ", " +
"actual: " + info.totalCpuDuration());
}
}
/**
* Spawn a child with arguments and check they are visible via the ProcessHandle.
*/
@Test
public static void test2() {
try {
long cpulooptime = 1 << 8;
String[] extraArgs = {"pid", "parent", "stdin"};
Instant beforeStart = Instant.now().truncatedTo(ChronoUnit.SECONDS);
JavaChild p1 = JavaChild.spawnJavaChild((Object[])extraArgs);
Instant afterStart = Instant.now();
try (BufferedReader lines = p1.outputReader()) {
Duration lastCpu = Duration.ofMillis(0L);
for (int j = 0; j < 20; j++) {
p1.sendAction("cpuloop", cpulooptime);
p1.sendAction("cputime", "");
// Read cputime from child
Duration childCpuTime = null;
// Read lines from the child until the result from cputime is returned
String s;
while ((s = lines.readLine()) != null) {
String[] split = s.trim().split(" ");
if (split.length == 3 && split[1].equals("cputime")) {
long nanos = Long.valueOf(split[2]);
childCpuTime = Duration.ofNanos(nanos);
break; // found the result we're looking for
}
}
ProcessHandle.Info info = p1.info();
System.out.printf(" info: %s%n", info);
if (info.user().isPresent()) {
String user = info.user().get();
Assert.assertNotNull(user, "User name");
Assert.assertEquals(user, whoami, "User name");
}
Optional<String> command = info.command();
if (command.isPresent()) {
String javaExe = System.getProperty("test.jdk") +
File.separator + "bin" + File.separator + "java";
String expected = Platform.isWindows() ? javaExe + ".exe" : javaExe;
Assert.assertEquals(command.get(), expected,
"Command: expected: 'java'" + ", actual: " + command);
}
if (info.arguments().isPresent()) {
String[] args = info.arguments().get();
if (Platform.isLinux() || Platform.isOSX()) {
int offset = args.length - extraArgs.length;
for (int i = 0; i < extraArgs.length; i++) {
Assert.assertEquals(args[offset + i], extraArgs[i],
"Actual argument mismatch, index: " + i);
}
} else if (Platform.isSolaris()) {
Assert.assertEquals(args.length, 1,
"Expected argument list length: 1");
Assert.assertNotNull(args[0],
"Expected an argument");
} else {
System.out.printf("No argument test for OS: %s%n", Platform.getOsName());
}
// Now check that the first argument is not the same as the executed command
if (args.length > 0) {
Assert.assertNotEquals(args[0], command,
"First argument should not be the executable: args[0]: "
+ args[0] + ", command: " + command);
}
}
if (info.totalCpuDuration().isPresent()) {
Duration totalCPU = info.totalCpuDuration().get();
Duration epsilon = Duration.ofMillis(200L);
Assert.assertTrue(totalCPU.toNanos() > 0L,
"total cpu time expected > 0ms, actual: " + totalCPU);
Assert.assertTrue(totalCPU.toNanos() < lastCpu.toNanos() + 10_000_000_000L,
"total cpu time expected < 10s more than previous iteration, actual: " + totalCPU);
if (childCpuTime != null) {
System.out.printf(" info.totalCPU: %s, childCpuTime: %s, diff: %s%n",
totalCPU.toNanos(), childCpuTime.toNanos(), childCpuTime.toNanos() - totalCPU.toNanos());
Assert.assertTrue(checkEpsilon(childCpuTime, totalCPU, epsilon),
childCpuTime + " should be within " +
epsilon + " of " + totalCPU);
}
lastCpu = totalCPU;
}
if (info.startInstant().isPresent()) {
Instant startTime = info.startInstant().get();
Assert.assertTrue(startTime.isBefore(afterStart),
"startTime after process spawn completed"
+ startTime + " + > " + afterStart);
}
}
}
p1.waitFor(5, TimeUnit.SECONDS);
} catch (IOException | InterruptedException ie) {
ie.printStackTrace(System.out);
Assert.fail("unexpected exception", ie);
}
}
/**
* Spawn a child with arguments and check they are visible via the ProcessHandle.
*/
@Test
public static void test3() {
try {
for (int sleepTime : Arrays.asList(1, 2)) {
Process p = spawn("sleep", String.valueOf(sleepTime));
ProcessHandle.Info info = p.info();
System.out.printf(" info: %s%n", info);
if (info.user().isPresent()) {
String user = info.user().get();
Assert.assertNotNull(user);
Assert.assertEquals(user, whoami);
}
if (info.command().isPresent()) {
String command = info.command().get();
String expected = Platform.isWindows() ? "sleep.exe" : "sleep";
Assert.assertTrue(command.endsWith(expected), "Command: expected: \'" +
expected + "\', actual: " + command);
// Verify the command exists and is executable
File exe = new File(command);
Assert.assertTrue(exe.exists(), "command must exist: " + exe);
Assert.assertTrue(exe.canExecute(), "command must be executable: " + exe);
}
if (info.arguments().isPresent()) {
String[] args = info.arguments().get();
if (args.length > 0) {
Assert.assertEquals(args[0], String.valueOf(sleepTime));
}
}
Assert.assertTrue(p.waitFor(15, TimeUnit.SECONDS));
}
} catch (IOException | InterruptedException ex) {
ex.printStackTrace(System.out);;
} finally {
// Destroy any children that still exist
ProcessUtil.destroyProcessTree(ProcessHandle.current());
}
}
/**
* Cross check the cputime reported from java.management with that for the current process.
*/
@Test
public static void test4() {
Duration myCputime1 = ProcessUtil.MXBeanCpuTime();
Optional<Duration> dur1 = ProcessHandle.current().info().totalCpuDuration();
Duration myCputime2 = ProcessUtil.MXBeanCpuTime();
Optional<Duration> dur2 = ProcessHandle.current().info().totalCpuDuration();
if (dur1.isPresent() && dur2.isPresent()) {
Duration total1 = dur1.get();
Duration total2 = dur2.get(); ;
System.out.printf(" total1 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n",
Objects.toString(total1), myCputime1, myCputime1.minus(total1));
System.out.printf(" total2 vs. mbean: %s, getProcessCpuTime: %s, diff: %s%n",
Objects.toString(total2), myCputime2, myCputime2.minus(total2));
Duration epsilon = Duration.ofMillis(200L); // Epsilon is 200ms.
Assert.assertTrue(checkEpsilon(myCputime1, myCputime2, epsilon),
myCputime1.toNanos() + " should be within " + epsilon
+ " of " + myCputime2.toNanos());
Assert.assertTrue(checkEpsilon(total1, total2, epsilon),
total1.toNanos() + " should be within " + epsilon
+ " of " + total2.toNanos());
Assert.assertTrue(checkEpsilon(myCputime1, total1, epsilon),
myCputime1.toNanos() + " should be within " + epsilon
+ " of " + total1.toNanos());
Assert.assertTrue(checkEpsilon(total1, myCputime2, epsilon),
total1.toNanos() + " should be within " + epsilon
+ " of " + myCputime2.toNanos());
Assert.assertTrue(checkEpsilon(myCputime2, total2, epsilon),
myCputime2.toNanos() + " should be within " + epsilon
+ " of " + total2.toNanos());
}
}
@Test
public static void test5() {
ProcessHandle self = ProcessHandle.current();
Random r = new Random();
for (int i = 0; i < 30; i++) {
Instant end = Instant.now().plusMillis(500L);
while (end.isBefore(Instant.now())) {
// burn the cpu time checking the time
long x = r.nextLong();
}
if (self.info().totalCpuDuration().isPresent()) {
Duration totalCpu = self.info().totalCpuDuration().get();
long infoTotalCputime = totalCpu.toNanos();
long beanCputime = ProcessUtil.MXBeanCpuTime().toNanos();
System.out.printf(" infoTotal: %12d, beanCpu: %12d, diff: %12d%n",
infoTotalCputime, beanCputime, beanCputime - infoTotalCputime);
} else {
break; // nothing to compare; continue
}
}
}
/**
* Check two Durations, the second should be greater than the first or
* within the supplied Epsilon.
* @param d1 a Duration - presumed to be shorter
* @param d2 a 2nd Duration - presumed to be greater (or within Epsilon)
* @param epsilon Epsilon the amount of overlap allowed
* @return
*/
static boolean checkEpsilon(Duration d1, Duration d2, Duration epsilon) {
if (d1.toNanos() <= d2.toNanos()) {
return true;
}
Duration diff = d1.minus(d2).abs();
return diff.compareTo(epsilon) <= 0;
}
/**
* Spawn a native process with the provided arguments.
* @param command the executable of native process
* @args
* @return the Process that was started
* @throws IOException thrown by ProcessBuilder.start
*/
static Process spawn(String command, String... args) throws IOException {
ProcessBuilder pb = new ProcessBuilder();
pb.inheritIO();
List<String> list = new ArrayList<>();
list.add(command);
for (String arg : args)
list.add(arg);
pb.command(list);
return pb.start();
}
}

View File

@ -0,0 +1,524 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import com.sun.management.OperatingSystemMXBean;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Reader;
import java.io.PrintWriter;
import java.lang.InterruptedException;
import java.lang.Override;
import java.lang.management.ManagementFactory;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.concurrent.CompletableFuture;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;
/**
* Command driven subprocess with useful child functions.
*/
public class JavaChild extends Process {
private static volatile int commandSeq = 0; // Command sequence number
private static final ProcessHandle self = ProcessHandle.current();
private static int finalStatus = 0;
private static final List<JavaChild> children = new ArrayList<>();
private static final Set<JavaChild> completedChildren =
Collections.synchronizedSet(new HashSet<>());
private final Process delegate;
private final PrintWriter inputWriter;
private final BufferedReader outputReader;
/**
* Create a JavaChild control instance that delegates to the spawned process.
* {@link #sendAction} is used to send commands via the processes stdin.
* {@link #forEachOutputLine} can be used to process output from the child
* @param delegate the process to delegate and send commands to and get responses from
*/
private JavaChild(Process delegate) {
this.delegate = delegate;
// Initialize PrintWriter with autoflush (on println)
inputWriter = new PrintWriter(delegate.getOutputStream(), true);
outputReader = new BufferedReader(new InputStreamReader(delegate.getInputStream()));
}
@Override
public void destroy() {
delegate.destroy();
}
@Override
public int exitValue() {
return delegate.exitValue();
}
@Override
public int waitFor() throws InterruptedException {
return delegate.waitFor();
}
@Override
public OutputStream getOutputStream() {
return delegate.getOutputStream();
}
@Override
public InputStream getInputStream() {
return delegate.getInputStream();
}
@Override
public InputStream getErrorStream() {
return delegate.getErrorStream();
}
@Override
public ProcessHandle toHandle() {
return delegate.toHandle();
}
@Override
public CompletableFuture<Process> onExit() {
return delegate.onExit();
}
@Override
public String toString() {
return "delegate: " + delegate.toString();
}
public CompletableFuture<JavaChild> onJavaChildExit() {
return onExit().thenApply(ph -> this);
}
/**
* Send an action and arguments to the child via stdin.
* @param action the action
* @param args additional arguments
* @throws IOException if something goes wrong writing to the child
*/
void sendAction(String action, Object... args) throws IOException {
StringBuilder sb = new StringBuilder();
sb.append(action);
for (Object arg :args) {
sb.append(" ");
sb.append(arg);
}
String cmd = sb.toString();
synchronized (this) {
inputWriter.println(cmd);
}
}
public BufferedReader outputReader() {
return outputReader;
}
/**
* Asynchronously evaluate each line of output received back from the child process.
* @param consumer a Consumer of each line read from the child
* @return a CompletableFuture that is completed when the child closes System.out.
*/
CompletableFuture<String> forEachOutputLine(Consumer<String> consumer) {
final CompletableFuture<String> future = new CompletableFuture<>();
String name = "OutputLineReader-" + getPid();
Thread t = new Thread(() -> {
try (BufferedReader reader = outputReader()) {
String line;
while ((line = reader.readLine()) != null) {
consumer.accept(line);
}
} catch (IOException | RuntimeException ex) {
consumer.accept("IOE (" + getPid() + "):" + ex.getMessage());
future.completeExceptionally(ex);
}
future.complete("success");
}, name);
t.start();
return future;
}
/**
* Spawn a JavaChild with the provided arguments.
* Commands can be send to the child with {@link #sendAction}.
* Output lines from the child can be processed with {@link #forEachOutputLine}.
* System.err is set to inherit and is the unstructured async logging
* output for all subprocesses.
* @param args the command line arguments to JavaChild
* @return the JavaChild that was started
* @throws IOException thrown by ProcessBuilder.start
*/
static JavaChild spawnJavaChild(Object... args) throws IOException {
String[] stringArgs = new String[args.length];
for (int i = 0; i < args.length; i++) {
stringArgs[i] = args[i].toString();
}
ProcessBuilder pb = build(stringArgs);
pb.redirectError(ProcessBuilder.Redirect.INHERIT);
return new JavaChild(pb.start());
}
/**
* Spawn a JavaChild with the provided arguments.
* Sets the process to inherit the I/O channels.
* @param args the command line arguments to JavaChild
* @return the Process that was started
* @throws IOException thrown by ProcessBuilder.start
*/
static Process spawn(String... args) throws IOException {
ProcessBuilder pb = build(args);
pb.inheritIO();
return pb.start();
}
/**
* Return a ProcessBuilder with the javaChildArgs and
* any additional supplied args.
*
* @param args the command line arguments to JavaChild
* @return the ProcessBuilder
*/
static ProcessBuilder build(String ... args) {
ProcessBuilder pb = new ProcessBuilder();
List<String> list = new ArrayList<>(javaChildArgs);
for (String arg : args)
list.add(arg);
pb.command(list);
return pb;
}
static final String javaHome = (System.getProperty("test.jdk") != null)
? System.getProperty("test.jdk")
: System.getProperty("java.home");
static final String javaExe =
javaHome + File.separator + "bin" + File.separator + "java";
static final String classpath =
System.getProperty("java.class.path");
static final List<String> javaChildArgs =
Arrays.asList(javaExe,
"-XX:+DisplayVMOutputToStderr",
"-Dtest.jdk=" + javaHome,
"-classpath", absolutifyPath(classpath),
"JavaChild");
private static String absolutifyPath(String path) {
StringBuilder sb = new StringBuilder();
for (String file : path.split(File.pathSeparator)) {
if (sb.length() != 0)
sb.append(File.pathSeparator);
sb.append(new File(file).getAbsolutePath());
}
return sb.toString();
}
/**
* Main program that interprets commands from the command line args or stdin.
* Each command produces output to stdout confirming the command and
* providing results.
* System.err is used for unstructured information.
* @param args an array of strings to be interpreted as commands;
* each command uses additional arguments as needed
*/
public static void main(String[] args) {
System.out.printf("args: %s %s%n", ProcessHandle.current(), Arrays.toString(args));
interpretCommands(args);
System.exit(finalStatus);
}
/**
* Interpret an array of strings as a command line.
* @param args an array of strings to be interpreted as commands;
* each command uses additional arguments as needed
*/
private static void interpretCommands(String[] args) {
try {
int nextArg = 0;
while (nextArg < args.length) {
String action = args[nextArg++];
switch (action) {
case "help":
sendResult(action, "");
help();
break;
case "sleep":
int millis = Integer.valueOf(args[nextArg++]);
Thread.sleep(millis);
sendResult(action, Integer.toString(millis));
break;
case "cpuloop":
long times = Long.valueOf(args[nextArg++]);
Instant end = Instant.now().plusMillis(times);
while (Instant.now().isBefore(end)) {
// burn the cpu til the time is up
}
sendResult(action, times);
break;
case "cputime":
sendResult(action, getCpuTime());
break;
case "out":
case "err":
String value = args[nextArg++];
sendResult(action, value);
if (action.equals("err")) {
System.err.println(value);
}
break;
case "stdin":
// Read commands from stdin; at eof, close stdin of
// children and wait for each to exit
sendResult(action, "start");
try (Reader reader = new InputStreamReader(System.in);
BufferedReader input = new BufferedReader(reader)) {
String line;
while ((line = input.readLine()) != null) {
line = line.trim();
if (!line.isEmpty()) {
String[] split = line.split("\\s");
interpretCommands(split);
}
}
// EOF on stdin, close stdin on all spawned processes
for (JavaChild p : children) {
try {
p.getOutputStream().close();
} catch (IOException ie) {
sendResult("stdin_closing", p.getPid(),
"exception", ie.getMessage());
}
}
for (JavaChild p : children) {
do {
try {
p.waitFor();
break;
} catch (InterruptedException e) {
// retry
}
} while (true);
}
// Wait for all children to be gone
Instant timeOut = Instant.now().plusSeconds(10L);
while (!completedChildren.containsAll(children)) {
if (Instant.now().isBefore(timeOut)) {
Thread.sleep(100L);
} else {
System.err.printf("Timeout waiting for " +
"children to terminate%n");
children.removeAll(completedChildren);
for (JavaChild c : children) {
sendResult("stdin_noterm", c.getPid());
System.err.printf(" Process not terminated: " +
"pid: %d%n", c.getPid());
}
System.exit(2);
}
}
}
sendResult(action, "done");
return; // normal exit from JavaChild Process
case "parent":
sendResult(action, self.parent().toString());
break;
case "pid":
sendResult(action, self.toString());
break;
case "exit":
int exitValue = (nextArg < args.length)
? Integer.valueOf(args[nextArg]) : 0;
sendResult(action, exitValue);
System.exit(exitValue);
break;
case "spawn": {
if (args.length - nextArg < 2) {
throw new RuntimeException("not enough args for respawn: " +
(args.length - 2));
}
// Spawn as many children as requested and
// pass on rest of the arguments
int ncount = Integer.valueOf(args[nextArg++]);
Object[] subargs = new String[args.length - nextArg];
System.arraycopy(args, nextArg, subargs, 0, subargs.length);
for (int i = 0; i < ncount; i++) {
JavaChild p = spawnJavaChild(subargs);
sendResult(action, p.getPid());
p.forEachOutputLine(JavaChild::sendRaw);
p.onJavaChildExit().thenAccept((p1) -> {
int excode = p1.exitValue();
sendResult("child_exit", p1.getPid(), excode);
completedChildren.add(p1);
});
children.add(p); // Add child to spawned list
}
nextArg = args.length;
break;
}
case "child": {
// Send the command to all the live children;
// ignoring those that are not alive
int sentCount = 0;
Object[] result =
Arrays.copyOfRange(args, nextArg - 1, args.length);
Object[] subargs =
Arrays.copyOfRange(args, nextArg + 1, args.length);
for (JavaChild p : children) {
if (p.isAlive()) {
sentCount++;
// overwrite with current pid
result[0] = Long.toString(p.getPid());
sendResult(action, result);
p.sendAction(args[nextArg], subargs);
}
}
if (sentCount == 0) {
sendResult(action, "n/a");
}
nextArg = args.length;
break;
}
case "child_eof" :
// Close the InputStream of all the live children;
// ignoring those that are not alive
for (JavaChild p : children) {
if (p.isAlive()) {
sendResult(action, p.getPid());
p.getOutputStream().close();
}
}
break;
case "property":
String name = args[nextArg++];
sendResult(action, name, System.getProperty(name));
break;
case "threaddump":
Thread.dumpStack();
break;
default:
throw new Error("JavaChild action unknown: " + action);
}
}
} catch (Throwable t) {
t.printStackTrace(System.err);
System.exit(1);
}
}
static synchronized void sendRaw(String s) {
System.out.println(s);
System.out.flush();
}
static void sendResult(String action, Object... results) {
sendRaw(new Event(action, results).toString());
}
static long getCpuTime() {
OperatingSystemMXBean osMbean =
(OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();
return osMbean.getProcessCpuTime();
}
/**
* Print command usage to stderr.
*/
private static void help() {
System.err.println("Commands:");
System.err.println(" help");
System.err.println(" pid");
System.err.println(" parent");
System.err.println(" cpuloop <loopcount>");
System.err.println(" cputime");
System.err.println(" stdin - read commands from stdin");
System.err.println(" sleep <millis>");
System.err.println(" spawn <n> command... - spawn n new children and send command");
System.err.println(" child command... - send command to all live children");
System.err.println(" child_eof - send eof to all live children");
System.err.println(" exit <exitcode>");
System.err.println(" out arg...");
System.err.println(" err arg...");
}
static class Event {
long pid;
long seq;
String command;
Object[] results;
Event(String command, Object... results) {
this(self.getPid(), ++commandSeq, command, results);
}
Event(long pid, int seq, String command, Object... results) {
this.pid = pid;
this.seq = seq;
this.command = command;
this.results = results;
}
/**
* Create a String encoding the pid, seq, command, and results.
*
* @return a String formatted to send to the stream.
*/
String format() {
StringBuilder sb = new StringBuilder();
sb.append(pid);
sb.append(":");
sb.append(seq);
sb.append(" ");
sb.append(command);
for (int i = 0; i < results.length; i++) {
sb.append(" ");
sb.append(results[i]);
}
return sb.toString();
}
Event(String encoded) {
String[] split = encoded.split("\\s");
String[] pidSeq = split[0].split(":");
pid = Long.valueOf(pidSeq[0]);
seq = Integer.valueOf(pidSeq[1]);
command = split[1];
Arrays.copyOfRange(split, 1, split.length);
}
public String toString() {
return format();
}
}
}

View File

@ -0,0 +1,193 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.lang.InterruptedException;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import jdk.testlibrary.Platform;
import org.testng.annotations.Test;
import org.testng.Assert;
import org.testng.TestNG;
/*
* @test
* @library /lib/testlibrary
* @summary Functions of Process.onExit and ProcessHandle.onExit
* @author Roger Riggs
*/
public class OnExitTest extends ProcessUtil {
@SuppressWarnings("raw_types")
public static void main(String[] args) {
Class<?>[] testclass = { OnExitTest.class};
TestNG testng = new TestNG();
testng.setTestClasses(testclass);
testng.run();
}
/**
* Basic test of exitValue and onExit.
*/
@Test
public static void test1() {
try {
int[] exitValues = {0, 1, 10};
for (int value : exitValues) {
Process p = JavaChild.spawn("exit", Integer.toString(value));
CompletableFuture<Process> future = p.onExit();
future.thenAccept( (ph) -> {
int actualExitValue = ph.exitValue();
printf(" javaChild done: %s, exitStatus: %d%n",
ph, actualExitValue);
Assert.assertEquals(actualExitValue, value, "actualExitValue incorrect");
Assert.assertEquals(ph, p, "Different Process passed to thenAccept");
});
Process h = future.get();
Assert.assertEquals(h, p);
Assert.assertEquals(p.exitValue(), value);
Assert.assertFalse(p.isAlive(), "Process should not be alive");
p.waitFor();
}
} catch (IOException | InterruptedException | ExecutionException ex) {
Assert.fail(ex.getMessage(), ex);
} finally {
destroyProcessTree(ProcessHandle.current());
}
}
/**
* Test of Completion handler when parent is killed.
* Spawn 1 child to spawn 3 children each with 2 children.
*/
@Test
public static void test2() {
try {
ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();
List<ProcessHandle> children = getChildren(ProcessHandle.current());
children.forEach(ProcessUtil::printProcess);
Assert.assertEquals(children.size(), 0,
"Expected to start with zero children; " + children);
JavaChild proc = JavaChild.spawnJavaChild("stdin");
ProcessHandle procHandle = proc.toHandle();
printf(" spawned: %d%n", proc.getPid());
proc.forEachOutputLine((s) -> {
String[] split = s.trim().split(" ");
if (split.length == 3 && split[1].equals("spawn")) {
Long child = Long.valueOf(split[2]);
Long parent = Long.valueOf(split[0].split(":")[0]);
processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get());
}
});
proc.sendAction("spawn", "3", "stdin");
proc.sendAction("child", "spawn", "2", "stdin");
// Poll until all 9 child processes exist or the timeout is reached
int expected = 9;
Instant endTimeout = Instant.now().plusSeconds(10L);
do {
Thread.sleep(200L);
printf(" subprocess count: %d, waiting for %d%n", processes.size(), expected);
} while (processes.size() < expected &&
Instant.now().isBefore(endTimeout));
children = getAllChildren(procHandle);
ArrayBlockingQueue<ProcessHandle> completions = new ArrayBlockingQueue<>(expected + 1);
Instant startTime = Instant.now();
// Create a future for each of the 9 children
processes.forEach( (p, parent) -> {
p.onExit().whenComplete((ph, ex) -> {
Duration elapsed = Duration.between(startTime, Instant.now());
completions.add(ph);
printf("whenComplete: pid: %s, exception: %s, thread: %s, elapsed: %s%n",
ph, ex, Thread.currentThread(), elapsed);
});
});
// Check that each of the spawned processes is included in the children
List<ProcessHandle> remaining = new ArrayList<>(children);
processes.forEach((p, parent) -> {
Assert.assertTrue(remaining.remove(p), "spawned process should have been in children");
});
// Remove Win32 system spawned conhost.exe processes
remaining.removeIf(ProcessUtil::isWindowsConsole);
remaining.forEach(p -> printProcess(p, "unexpected: "));
if (remaining.size() > 0) {
// Show full list for debugging
ProcessUtil.logTaskList();
}
proc.destroy(); // kill off the parent
proc.waitFor();
// Wait for all the processes to be completed
processes.forEach((p, parent) -> {
try {
p.onExit().get();
} catch (InterruptedException | ExecutionException ex) {
// ignore
}
});
// Verify that all 9 exit handlers were called
processes.forEach((p, parent) ->
Assert.assertTrue(completions.contains(p), "Child onExit not called: " + p
+ ", parent: " + parent
+ ": " + p.info()));
// Show the status of the original children
children.forEach(p -> printProcess(p, "after onExit:"));
Assert.assertEquals(proc.isAlive(), false, "destroyed process is alive:: %s%n" + proc);
List<ProcessHandle> children2 = getAllChildren(procHandle);
printf(" children2: %s%n", children2.toString());
Assert.assertEquals(children2.size(), 0, "After onExit, expected no children");
Assert.assertEquals(remaining.size(), 0, "Unaccounted for children");
} catch (IOException | InterruptedException ex) {
Assert.fail(ex.getMessage());
} finally {
destroyProcessTree(ProcessHandle.current());
}
}
}

View File

@ -0,0 +1,222 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.FilePermission;
import java.io.IOException;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.security.SecurityPermission;
import java.util.Arrays;
import java.util.Optional;
import java.util.PropertyPermission;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeGroups;
import org.testng.annotations.Test;
public class PermissionTest {
/**
* Backing up policy.
*/
protected static Policy policy;
/**
* Backing up security manager.
*/
private static SecurityManager sm;
/**
* Current process handle.
*/
private final ProcessHandle currentHndl;
PermissionTest() {
policy = Policy.getPolicy();
sm = System.getSecurityManager();
currentHndl = ProcessHandle.current();
}
@Test
public void allChildrenWithPermission() {
Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
currentHndl.allChildren();
}
@Test
public void allProcessesWithPermission() {
Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
ProcessHandle.allProcesses();
}
@Test
public void childrenWithPermission() {
Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
currentHndl.children();
}
@Test
public void currentWithPermission() {
Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
ProcessHandle.current();
}
@Test
public void ofWithPermission() {
Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
ProcessHandle.of(0);
}
@Test
public void parentWithPermission() {
Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
currentHndl.parent();
}
@Test
public void processToHandleWithPermission() throws IOException {
Policy.setPolicy(new TestPolicy(new RuntimePermission("manageProcess")));
Process p = null;
try {
ProcessBuilder pb = new ProcessBuilder("sleep", "30");
p = pb.start();
ProcessHandle ph = p.toHandle();
Assert.assertNotNull(ph, "ProcessHandle expected from Process");
} finally {
if (p != null) {
p.destroy();
}
}
}
@BeforeGroups (groups = {"NoManageProcessPermission"})
public void noPermissionsSetup(){
Policy.setPolicy(new TestPolicy());
SecurityManager sm = new SecurityManager();
System.setSecurityManager(sm);
}
@Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
public void noPermissionAllChildren() {
currentHndl.allChildren();
}
@Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
public void noPermissionAllProcesses() {
ProcessHandle.allProcesses();
}
@Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
public void noPermissionChildren() {
currentHndl.children();
}
@Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
public void noPermissionCurrent() {
ProcessHandle.current();
}
@Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
public void noPermissionOf() {
ProcessHandle.of(0);
}
@Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
public void noPermissionParent() {
currentHndl.parent();
}
@Test(groups = { "NoManageProcessPermission" }, expectedExceptions = SecurityException.class)
public void noPermissionProcessToHandle() throws IOException {
Process p = null;
try {
ProcessBuilder pb = new ProcessBuilder("sleep", "30");
p = pb.start();
ProcessHandle ph = p.toHandle();
Assert.assertNotNull(ph, "ProcessHandle expected from Process");
} finally {
if (p != null) {
p.destroy();
}
}
}
@AfterClass
public void tearDownClass() throws Exception {
System.setSecurityManager(sm);
Policy.setPolicy(policy);
}
}
class TestPolicy extends Policy {
private final PermissionCollection permissions = new Permissions();
public TestPolicy() {
setBasicPermissions();
}
/*
* Defines the minimal permissions required by testNG and set security
* manager permission when running these tests.
*/
public void setBasicPermissions() {
permissions.add(new SecurityPermission("getPolicy"));
permissions.add(new SecurityPermission("setPolicy"));
permissions.add(new RuntimePermission("getClassLoader"));
permissions.add(new RuntimePermission("setSecurityManager"));
permissions.add(new RuntimePermission("createSecurityManager"));
permissions.add(new PropertyPermission("testng.show.stack.frames",
"read"));
permissions.add(new PropertyPermission("user.dir", "read"));
permissions.add(new PropertyPermission("test.src", "read"));
permissions.add(new PropertyPermission("file.separator", "read"));
permissions.add(new PropertyPermission("line.separator", "read"));
permissions.add(new PropertyPermission("fileStringBuffer", "read"));
permissions.add(new PropertyPermission("dataproviderthreadcount", "read"));
permissions.add(new FilePermission("<<ALL FILES>>", "execute"));
}
public TestPolicy(Permission... ps) {
setBasicPermissions();
Arrays.stream(ps).forEach(p -> permissions.add(p));
}
@Override
public PermissionCollection getPermissions(ProtectionDomain domain) {
return permissions;
}
@Override
public PermissionCollection getPermissions(CodeSource codesource) {
return permissions;
}
@Override
public boolean implies(ProtectionDomain domain, Permission perm) {
return permissions.implies(perm);
}
}

View File

@ -0,0 +1,246 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.lang.ProcessBuilder;
import java.time.Duration;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import com.sun.management.OperatingSystemMXBean;
import jdk.testlibrary.Platform;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Useful utilities for testing Process and ProcessHandle.
*/
public abstract class ProcessUtil {
/**
* Constructor
*/
public ProcessUtil() {}
/**
* Returns the list of direct children.
* WIndows conhost.exe children are filtered out.
* @param ph the Process to get children of
* @return a list of child ProcessHandles
*/
public static List<ProcessHandle> getChildren(ProcessHandle ph) {
return ph.children()
.filter(ProcessUtil::isNotWindowsConsole)
.collect(Collectors.toList());
}
/**
* Returns the list of all direct and indirect children.
* WIndows conhost.exe children are filtered out.
* @param ph the Process to get children of
* @return a list of child ProcessHandles
*/
public static List<ProcessHandle> getAllChildren(ProcessHandle ph) {
return ph.allChildren()
.filter(ProcessUtil::isNotWindowsConsole)
.collect(Collectors.toList());
}
/**
* Waits for and returns the direct expected Children of a ProcessHandle.
* For Windows, the conhost.exe children are filtered out.
*
* @param ph the process to get the children of
* @param nchildren the minimum number of children to expect
* @return a list of ProcessHandles of the children.
*/
public static List<ProcessHandle> waitForChildren(ProcessHandle ph, long nchildren) {
List<ProcessHandle> subprocesses = null;
long count = 0;
do {
if (subprocesses != null) {
// Only wait if this is not the first time looking
try {
Thread.sleep(500L); // It will happen but don't burn the cpu
} catch (InterruptedException ie) {
// ignore
}
}
subprocesses = getChildren(ph);
count = subprocesses.size();
System.out.printf(" waiting for subprocesses of %s to start," +
" expected: %d, current: %d%n", ph, nchildren, count);
} while (count < nchildren);
return subprocesses;
}
/**
* Waits for and returns all expected Children of a ProcessHandle.
* For Windows, the conhost.exe children are filtered out.
*
* @param ph the process to get the children of
* @param nchildren the minimum number of children to expect
* @return a list of ProcessHandles of the children.
*/
public static List<ProcessHandle> waitForAllChildren(ProcessHandle ph, long nchildren) {
List<ProcessHandle> subprocesses = null;
long count = 0;
do {
if (subprocesses != null) {
// Only wait if this is not the first time looking
try {
Thread.sleep(500L); // It will happen but don't burn the cpu
} catch (InterruptedException ie) {
// ignore
}
}
subprocesses = getAllChildren(ph);
count = subprocesses.size();
System.out.printf(" waiting for subprocesses of %s to start," +
" expected: %d, current: %d%n", ph, nchildren, count);
} while (count < nchildren);
return subprocesses;
}
/**
* Destroy all children of the ProcessHandle.
* (Except the conhost.exe on Windows)
*
* @param p a ProcessHandle
* @return the ProcessHandle
*/
public static ProcessHandle destroyProcessTree(ProcessHandle p) {
Stream<ProcessHandle> children = p.allChildren().filter(ProcessUtil::isNotWindowsConsole);
children.forEach(ph -> {
System.out.printf("destroyProcessTree destroyForcibly%n");
printProcess(ph);
ph.destroyForcibly();
});
return p;
}
/**
* The OSMXBean for this process.
*/
public static final OperatingSystemMXBean osMbean =
(OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();
/**
* Return the CPU time of the current process according to the OperatingSystemMXBean.
*
* @return the CPU time of the current process
*/
public static Duration MXBeanCpuTime() {
return Duration.ofNanos(osMbean.getProcessCpuTime());
}
/**
* Return true if the ProcessHandle is a Windows i586 conhost.exe process.
*
* @param p the processHandle of the Process
* @return Return true if the ProcessHandle is for a Windows i586 conhost.exe process
*/
static boolean isWindowsConsole(ProcessHandle p) {
return Platform.isWindows() && p.info().command().orElse("").endsWith("C:\\Windows\\System32\\conhost.exe");
}
/**
* Return true if the ProcessHandle is NOT a Windows i586 conhost.exe process.
*
* @param p the processHandle of the Process
* @return Return true if the ProcessHandle is NOT for a Windows i586 conhost.exe process
*/
static boolean isNotWindowsConsole(ProcessHandle p) {
return !isWindowsConsole(p);
}
/**
* Print a formatted string to System.out.
* @param format the format
* @param args the argument array
*/
static void printf(String format, Object... args) {
String s = String.format(format, args);
System.out.print(s);
}
/**
* Print information about a process.
* Prints the pid, if it is alive, and information about the process.
* @param ph the processHandle at the top
*/
static void printProcess(ProcessHandle ph) {
printProcess(ph, "");
}
/**
* Print information about a process.
* Prints the pid, if it is alive, and information about the process.
* @param ph the processHandle at the top
* @param prefix the String to prefix the output with
*/
static void printProcess(ProcessHandle ph, String prefix) {
printf("%spid %s, alive: %s; parent: %s, %s%n", prefix,
ph.getPid(), ph.isAlive(), ph.parent(), ph.info());
}
/**
* Print the process hierarchy as visible via ProcessHandle.
* Prints the pid, if it is alive, and information about the process.
* @param ph the processHandle at the top
* @param prefix the String to prefix the output with
*/
static void printDeep(ProcessHandle ph, String prefix) {
printProcess(ph, prefix);
ph.children().forEach(p -> printDeep(p, prefix + " "));
}
/**
* Use the native command to list the active processes.
*/
static void logTaskList() {
String[] windowsArglist = {"tasklist.exe", "/v"};
String[] unixArglist = {"ps", "-ef"};
String[] argList = null;
if (Platform.isWindows()) {
argList = windowsArglist;
} else if (Platform.isLinux() || Platform.isOSX()) {
argList = unixArglist;
} else {
return;
}
ProcessBuilder pb = new ProcessBuilder(argList);
pb.inheritIO();
try {
Process proc = pb.start();
proc.waitFor();
} catch (IOException | InterruptedException ex) {
ex.printStackTrace();
}
}
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import static org.testng.Assert.assertEquals;
import org.testng.TestNG;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/*
* @test
* @summary Scalibities test for checking scability of ProcessHandle
* @run testng/othervm ScaleTest
*/
public class ScaleTest {
/**
* Scale max processes number to 5000.
*/
private final static int MAX_PROCESSES_LIMIT = 1000;
/**
* Create test process number as 1, 2, 5, 10, 20, 50, 100, 200... until it reach
* setting process limit.
* @return iterator on how many processes can be created.
* @throws IOException if can't create processes.
*/
@DataProvider
public Iterator<Object[]> processesNumbers() {
// Limit spawn processes number less than total limitation.
List<Object[]> testProcesses = new ArrayList<>();
int i = 1, j = 0;
while (i <= MAX_PROCESSES_LIMIT) {
testProcesses.add(new Object[]{i});
if ((j % 3) != 1)
i *= 2;
else
i = i * 25 / 10;
j++;
}
return testProcesses.iterator();
}
/**
* Start process by given number, compare created processes with
* ProcessHandle.children by order.
* @param processNum processes number that will be created
*/
@Test(dataProvider = "processesNumbers")
public void scaleProcesses(int processNum) {
try {
ProcessBuilder pb = new ProcessBuilder();
pb.command("sleep", "600");
List<ProcessHandle> children = new ArrayList<>();
int createdProcessNum = 0;
for (int i = 0; i < processNum; i++) {
try {
children.add(pb.start().toHandle());
createdProcessNum++;
} catch (Throwable ignore) {
// Hard to control how many processes we can generate.
// Ignore every error when create new process
}
}
List<ProcessHandle> phs = ProcessHandle.current().allChildren()
.filter(ph -> ph.info().command().orElse("").contains("sleep"))
.collect(Collectors.toList());
assertEquals(phs.size(), createdProcessNum, "spawned processes vs allChildren");
assertEqualsWithoutOrder(phs, children, ProcessHandle::compareTo, processNum);
} finally {
ProcessHandle.current().children().forEach(ProcessHandle::destroyForcibly);
}
}
/**
* Sort two list by given comparator and compare two list without order
* @param actual Process handle list1
* @param expected Process handle list1
* @param comp ProcessHandle comparator for sorting
* @param pn number of processes
*/
private void assertEqualsWithoutOrder(List<ProcessHandle> actual,
List<ProcessHandle> expected, Comparator<ProcessHandle> comp, int pn) {
Collections.sort(actual, comp);
Collections.sort(expected, comp);
assertEquals(actual, expected);
}
// Main can be used to run the tests from the command line with only testng.jar.
@SuppressWarnings("raw_types")
public static void main(String[] args) {
Class<?>[] testclass = {ScaleTest.class};
TestNG testng = new TestNG();
testng.setTestClasses(testclass);
testng.run();
}
}

View File

@ -0,0 +1,4 @@
# ProcessHandle tests use TestNG
TestNG.dirs = .
lib.dirs = /lib/testlibrary

View File

@ -0,0 +1,358 @@
/*
* Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
import java.io.IOException;
import java.util.ArrayList;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.concurrent.ExecutionException;
import org.testng.Assert;
import org.testng.TestNG;
import org.testng.annotations.Test;
/*
* @test
* @library /lib/testlibrary
* Test counting and JavaChild.spawning and counting of Processes.
* @run testng/othervm InfoTest
* @author Roger Riggs
*/
public class TreeTest extends ProcessUtil {
// Main can be used to run the tests from the command line with only testng.jar.
@SuppressWarnings("raw_types")
public static void main(String[] args) {
Class<?>[] testclass = {TreeTest.class};
TestNG testng = new TestNG();
testng.setTestClasses(testclass);
testng.run();
}
/**
* Test counting and spawning and counting of Processes.
*/
@Test
public static void test1() {
final int MAXCHILDREN = 2;
List<JavaChild> spawned = new ArrayList<>();
try {
ProcessHandle self = ProcessHandle.current();
printf("self pid: %d%n", self.getPid());
printDeep(self, "");
long count = getChildren(self).size();
Assert.assertEquals(count, 0, "Start with zero children");
for (int i = 0; i < MAXCHILDREN; i++) {
// spawn and wait for instructions
spawned.add(JavaChild.spawnJavaChild("pid", "stdin"));
}
List<ProcessHandle> subprocesses = getChildren(self);
subprocesses.forEach(ProcessUtil::printProcess);
count = subprocesses.size();
Assert.assertEquals(count, MAXCHILDREN, "Wrong number of spawned children");
// Send exit command to each spawned Process
spawned.forEach(p -> {
try {
p.sendAction("exit", "");
} catch (IOException ex) {
Assert.fail("IOException in sendAction", ex);
}
});
// Wait for each Process to exit
spawned.forEach(p -> {
do {
try {
Assert.assertEquals(p.waitFor(), 0, "exit status incorrect");
break;
} catch (InterruptedException ex) {
continue; // Retry
}
} while (true);
});
// Verify that ProcessHandle.isAlive sees each of them as not alive
for (ProcessHandle ph : subprocesses) {
Assert.assertFalse(ph.isAlive(),
"ProcessHandle.isAlive for exited process: " + ph);
}
// Verify no current children are visible
count = getChildren(self).size();
Assert.assertEquals(count, 0, "Children destroyed, should be zero");
} catch (IOException ioe) {
Assert.fail("unable to spawn process", ioe);
} finally {
// Cleanup any left over processes
spawned.stream().map(Process::toHandle)
.filter(ProcessHandle::isAlive)
.forEach(ph -> printDeep(ph, "test1 cleanup: "));
destroyProcessTree(ProcessHandle.current());
}
}
/**
* Test counting and spawning and counting of Processes.
*/
@Test
public static void test2() {
ProcessHandle p1Handle = null;
try {
ProcessHandle self = ProcessHandle.current();
List<ProcessHandle> initialChildren = getChildren(self);
long count = initialChildren.size();
if (count > 0) {
initialChildren.forEach(p -> printDeep(p, "test2 initial unexpected: "));
Assert.assertEquals(count, 0, "Start with zero children (except Windows conhost.exe)");
}
JavaChild p1 = JavaChild.spawnJavaChild("stdin");
p1Handle = p1.toHandle();
printf(" p1 pid: %d%n", p1.getPid());
int spawnNew = 3;
p1.sendAction("spawn", spawnNew, "stdin");
// Wait for direct children to be created and save the list
List<ProcessHandle> subprocesses = waitForAllChildren(p1Handle, spawnNew);
for (ProcessHandle ph : subprocesses) {
Assert.assertTrue(ph.isAlive(), "Child should be alive: " + ph);
}
// Each child spawns two processes and waits for commands
int spawnNewSub = 2;
p1.sendAction("child", "spawn", spawnNewSub, "stdin");
// For each spawned child, wait for its children
for (ProcessHandle p : subprocesses) {
List<ProcessHandle> grandChildren = waitForChildren(p, spawnNewSub);
}
List<ProcessHandle> allChildren = getAllChildren(p1Handle);
printf(" allChildren: %s%n",
allChildren.stream().map(p -> p.getPid())
.collect(Collectors.toList()));
for (ProcessHandle ph : allChildren) {
Assert.assertEquals(ph.isAlive(), true, "Child should be alive: " + ph);
}
// Closing JavaChild's InputStream will cause all children to exit
p1.getOutputStream().close();
for (ProcessHandle p : allChildren) {
try {
p.onExit().get(); // wait for the child to exit
} catch (ExecutionException e) {
Assert.fail("waiting for process to exit", e);
}
}
p1.waitFor(); // wait for spawned process to exit
List<ProcessHandle> remaining = getChildren(self);
remaining.forEach(ph -> Assert.assertFalse(ph.isAlive(),
"process should not be alive: " + ph));
} catch (IOException | InterruptedException t) {
t.printStackTrace();
throw new RuntimeException(t);
} finally {
// Cleanup any left over processes
if (p1Handle.isAlive()) {
printDeep(p1Handle, "test2 cleanup: ");
}
destroyProcessTree(ProcessHandle.current());
}
}
/**
* Test destroy of processes.
*/
@Test
public static void test3() {
try {
ProcessHandle self = ProcessHandle.current();
JavaChild p1 = JavaChild.spawnJavaChild("stdin");
ProcessHandle p1Handle = p1.toHandle();
printf(" p1: %s%n", p1.getPid());
long count = getChildren(self).size();
Assert.assertEquals(count, 1, "Wrong number of spawned children");
int newChildren = 3;
// Spawn children and have them wait
p1.sendAction("spawn", newChildren, "stdin");
// Wait for the new processes and save the list
List<ProcessHandle> subprocesses = waitForAllChildren(p1Handle, newChildren);
printDeep(p1Handle, "allChildren");
Assert.assertEquals(subprocesses.size(), newChildren, "Wrong number of children");
p1.children().filter(TreeTest::isNotWindowsConsole)
.forEach(ProcessHandle::destroyForcibly);
self.children().filter(TreeTest::isNotWindowsConsole)
.forEach(ProcessHandle::destroyForcibly);
do {
Thread.sleep(500L); // It will happen but don't burn the cpu
Object[] children = self.allChildren()
.filter(TreeTest::isNotWindowsConsole)
.toArray();
count = children.length;
printf(" waiting for subprocesses of %s to terminate," +
" expected: 0, current: %d, children: %s%n", self, count,
Arrays.toString(children));
printDeep(self, "");
} while (count > 0);
boolean ex1 = p1.waitFor(5, TimeUnit.SECONDS);
Assert.assertTrue(ex1, "Subprocess should have exited: " + p1);
for (ProcessHandle p : subprocesses) {
Assert.assertFalse(p.isAlive(), "Destroyed process.isAlive: " + p +
", parent: " + p.parent() +
", info: " + p.info().toString());
}
} catch (IOException ioe) {
Assert.fail("Spawn of subprocess failed", ioe);
} catch (InterruptedException inte) {
Assert.fail("InterruptedException", inte);
}
}
/**
* Test (Not really a test) that dumps the list of all Processes.
*/
@Test
public static void test4() {
printf(" Parent Child Info%n");
Stream<ProcessHandle> s = ProcessHandle.allProcesses();
ProcessHandle[] processes = s.toArray(ProcessHandle[]::new);
int len = processes.length;
ProcessHandle[] parent = new ProcessHandle[len];
Set<ProcessHandle> processesSet =
Arrays.stream(processes).collect(Collectors.toSet());
Integer[] sortindex = new Integer[len];
for (int i = 0; i < len; i++) {
sortindex[i] = i;
}
for (int i = 0; i < len; i++) {
parent[sortindex[i]] = processes[sortindex[i]].parent().orElse(null);
}
Arrays.sort(sortindex, (i1, i2) -> {
int cmp = Long.compare((parent[i1] == null ? 0L : parent[i1].getPid()),
(parent[i2] == null ? 0L : parent[i2].getPid()));
if (cmp == 0) {
cmp = Long.compare((processes[i1] == null ? 0L : processes[i1].getPid()),
(processes[i2] == null ? 0L : processes[i2].getPid()));
}
return cmp;
});
boolean fail = false;
for (int i = 0; i < len; i++) {
ProcessHandle p = processes[sortindex[i]];
ProcessHandle p_parent = parent[sortindex[i]];
ProcessHandle.Info info = p.info();
String indent = " ";
if (p_parent != null) {
if (!processesSet.contains(p_parent)) {
fail = true;
indent = "*** ";
}
}
printf("%s %7s, %7s, %s%n", indent, p_parent, p, info);
}
Assert.assertFalse(fail, "Parents missing from all Processes");
}
/**
* A test for scale; launch a large number (39) of subprocesses.
*/
@Test
public static void test5() {
int factor = 2;
ProcessHandle p1Handle = null;
Instant start = Instant.now();
try {
JavaChild p1 = JavaChild.spawnJavaChild("stdin");
p1Handle = p1.toHandle();
printf("Spawning %d x %d x %d processes, pid: %d%n",
factor, factor, factor, p1.getPid());
// Start the first tier of subprocesses
p1.sendAction("spawn", factor, "stdin");
// Start the second tier of subprocesses
p1.sendAction("child", "spawn", factor, "stdin");
// Start the third tier of subprocesses
p1.sendAction("child", "child", "spawn", factor, "stdin");
int newChildren = factor * (1 + factor * (1 + factor));
List<ProcessHandle> children = ProcessUtil.waitForAllChildren(p1Handle, newChildren);
Assert.assertEquals(p1.children()
.filter(ProcessUtil::isNotWindowsConsole)
.count(), factor, "expected direct children");
Assert.assertEquals(p1.allChildren()
.filter(ProcessUtil::isNotWindowsConsole)
.count(),
factor * factor * factor + factor * factor + factor,
"expected all children");
List<ProcessHandle> subprocesses = p1.allChildren()
.filter(ProcessUtil::isNotWindowsConsole)
.collect(Collectors.toList());
printf(" allChildren: %s%n",
subprocesses.stream().map(p -> p.getPid())
.collect(Collectors.toList()));
p1.getOutputStream().close(); // Close stdin for the controlling p1
p1.waitFor();
} catch (InterruptedException | IOException ex) {
Assert.fail("Unexpected Exception", ex);
} finally {
printf("Duration: %s%n", Duration.between(start, Instant.now()));
// Cleanup any left over processes
if (p1Handle.isAlive()) {
printDeep(p1Handle, "test5 cleanup: ");
}
destroyProcessTree(ProcessHandle.current());
}
}
}