8077350: JEP 102 Process API Updates Implementation
Reviewed-by: chegar, plevart, psandoz, darcy, martin, alanb
This commit is contained in:
parent
5fbfe34bf6
commit
103d99baf1
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
361
jdk/src/java.base/share/classes/java/lang/ProcessHandle.java
Normal file
361
jdk/src/java.base/share/classes/java/lang/ProcessHandle.java
Normal 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);
|
||||
|
||||
}
|
528
jdk/src/java.base/share/classes/java/lang/ProcessHandleImpl.java
Normal file
528
jdk/src/java.base/share/classes/java/lang/ProcessHandleImpl.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
769
jdk/src/java.base/unix/native/libjava/ProcessHandleImpl_unix.c
Normal file
769
jdk/src/java.base/unix/native/libjava/ProcessHandleImpl_unix.c
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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)
|
||||
|
426
jdk/src/java.base/windows/native/libjava/ProcessHandleImpl_win.c
Normal file
426
jdk/src/java.base/windows/native/libjava/ProcessHandleImpl_win.c
Normal 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);
|
||||
}
|
@ -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
|
||||
|
@ -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);}
|
||||
|
143
jdk/test/java/lang/ProcessHandle/Basic.java
Normal file
143
jdk/test/java/lang/ProcessHandle/Basic.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
350
jdk/test/java/lang/ProcessHandle/InfoTest.java
Normal file
350
jdk/test/java/lang/ProcessHandle/InfoTest.java
Normal 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();
|
||||
}
|
||||
}
|
524
jdk/test/java/lang/ProcessHandle/JavaChild.java
Normal file
524
jdk/test/java/lang/ProcessHandle/JavaChild.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
193
jdk/test/java/lang/ProcessHandle/OnExitTest.java
Normal file
193
jdk/test/java/lang/ProcessHandle/OnExitTest.java
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
222
jdk/test/java/lang/ProcessHandle/PermissionTest.java
Normal file
222
jdk/test/java/lang/ProcessHandle/PermissionTest.java
Normal 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);
|
||||
}
|
||||
}
|
246
jdk/test/java/lang/ProcessHandle/ProcessUtil.java
Normal file
246
jdk/test/java/lang/ProcessHandle/ProcessUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
126
jdk/test/java/lang/ProcessHandle/ScaleTest.java
Normal file
126
jdk/test/java/lang/ProcessHandle/ScaleTest.java
Normal 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();
|
||||
}
|
||||
}
|
4
jdk/test/java/lang/ProcessHandle/TEST.properties
Normal file
4
jdk/test/java/lang/ProcessHandle/TEST.properties
Normal file
@ -0,0 +1,4 @@
|
||||
# ProcessHandle tests use TestNG
|
||||
TestNG.dirs = .
|
||||
lib.dirs = /lib/testlibrary
|
||||
|
358
jdk/test/java/lang/ProcessHandle/TreeTest.java
Normal file
358
jdk/test/java/lang/ProcessHandle/TreeTest.java
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user