blob: d634166a30a5bfc1bb4d60fe737e209893ae2c15 [file] [log] [blame]
/*
* Copyright (c) 2014, 2017, 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.lang.annotation.Native;
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.ThreadFactory;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import static java.security.AccessController.doPrivileged;
/**
* ProcessHandleImpl is the implementation of ProcessHandle.
*
* @see Process
* @since 9
*/
final class ProcessHandleImpl implements ProcessHandle {
/**
* Default size of stack for reaper processes.
*/
private static long REAPER_DEFAULT_STACKSIZE = 128 * 1024;
/**
* Return value from waitForProcessExit0 indicating the process is not a child.
*/
@Native
private static final int NOT_A_CHILD = -2;
/**
* Cache the ProcessHandle of this process.
*/
private static final ProcessHandleImpl current;
/**
* Map of pids to ExitCompletions.
*/
private static final ConcurrentMap<Long, ExitCompletion>
completions = new ConcurrentHashMap<>();
static {
initNative();
long pid = getCurrentPid0();
current = new ProcessHandleImpl(pid, isAlive0(pid));
}
private static native void initNative();
/**
* 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;
final long stackSize = Boolean.getBoolean("jdk.lang.processReaperUseDefaultStackSize")
? 0 : REAPER_DEFAULT_STACKSIZE;
ThreadFactory threadFactory = grimReaper -> {
Thread t = new Thread(systemThreadGroup, grimReaper,
"process reaper", stackSize, false);
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;
}
}
/**
* 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);
if (exitValue == NOT_A_CHILD) {
// pid not alive or not a child of this process
// If it is alive wait for it to terminate
long sleep = 300; // initial milliseconds to sleep
int incr = 30; // increment to the sleep time
long startTime = isAlive0(pid);
long origStart = startTime;
while (startTime >= 0) {
try {
Thread.sleep(Math.min(sleep, 5000L)); // no more than 5 sec
sleep += incr;
} catch (InterruptedException ie) {
// ignore and retry
}
startTime = isAlive0(pid); // recheck if is alive
if (origStart > 0 && startTime != origStart) {
// start time changed, pid is not the same process
break;
}
}
exitValue = 0;
}
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(pid(), 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);
/**
* The pid of this ProcessHandle.
*/
private final long pid;
/**
* The start time of this process.
* If STARTTIME_ANY, the start time of the process is not available from the os.
* If greater than zero, the start time of the process.
*/
private final long startTime;
/* The start time should match any value.
* Typically, this is because the OS can not supply it.
* The process is known to exist but not the exact start time.
*/
private final long STARTTIME_ANY = 0L;
/* The start time of a Process that does not exist. */
private final long STARTTIME_PROCESS_UNKNOWN = -1;
/**
* Private constructor. Instances are created by the {@code get(long)} factory.
* @param pid the pid for this instance
*/
private ProcessHandleImpl(long pid, long startTime) {
this.pid = pid;
this.startTime = startTime;
}
/**
* 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"));
}
long start = isAlive0(pid);
return (start >= 0)
? Optional.of(new ProcessHandleImpl(pid, start))
: Optional.empty();
}
/**
* Returns a ProcessHandle for an existing native process known to be alive.
* The startTime of the process is retrieved and stored in the ProcessHandle.
* It does not perform a security check since it is called from ProcessImpl.
* @param pid of the known to exist process
* @return a ProcessHandle corresponding to an existing Process instance
*/
static ProcessHandleImpl getInternal(long pid) {
return new ProcessHandleImpl(pid, isAlive0(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 pid() {
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
*/
public Optional<ProcessHandle> parent() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
long ppid = parent0(pid, startTime);
if (ppid <= 0) {
return Optional.empty();
}
return get(ppid);
}
/**
* Returns the parent of the native pid argument.
*
* @param pid the process id
* @param startTime the startTime of the process
* @return the parent of the native pid; if any, otherwise -1
*/
private static native long parent0(long pid, long startTime);
/**
* 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
* @param starttimes an allocated long array to receive the child start times; 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, long[] starttimes);
/**
* Destroy the process for this ProcessHandle.
* The native code checks the start time before sending the termination request.
*
* @param force {@code true} if the process should be terminated forcibly;
* else {@code false} for a normal termination
*/
boolean destroyProcess(boolean force) {
if (this.equals(current)) {
throw new IllegalStateException("destroy of current process not allowed");
}
return destroy0(pid, startTime, force);
}
/**
* Signal the process to terminate.
* The process is signaled only if its start time matches the known start time.
*
* @param pid process id to kill
* @param startTime the start time of the process
* @param forcibly true to forcibly terminate (SIGKILL vs SIGTERM)
* @return true if the process was signaled without error; false otherwise
*/
private static native boolean destroy0(long pid, long startTime, boolean forcibly);
@Override
public boolean destroy() {
return destroyProcess(false);
}
@Override
public boolean destroyForcibly() {
return destroyProcess(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 9
*/
@Override
public boolean isAlive() {
long start = isAlive0(pid);
return (start >= 0 && (start == startTime || start == 0 || startTime == 0));
}
/**
* Returns the process start time depending on whether the pid is alive.
* This must not reap the exitValue.
*
* @param pid the pid to check
* @return the start time in milliseconds since 1970,
* 0 if the start time cannot be determined,
* -1 if the pid does not exist.
*/
private static native long isAlive0(long pid);
@Override
public Stream<ProcessHandle> children() {
// The native OS code selects based on matching the requested parent pid.
// If the original parent exits, the pid may have been re-used for
// this newer process.
// Processes started by the original parent (now dead) will all have
// start times less than the start of this newer parent.
// Processes started by this newer parent will have start times equal
// or after this parent.
return children(pid).filter(ph -> startTime <= ((ProcessHandleImpl)ph).startTime);
}
/**
* 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;
long[] starttimes = null;
while (childpids == null || size > childpids.length) {
childpids = new long[size];
starttimes = new long[size];
size = getProcessPids0(pid, childpids, null, starttimes);
}
final long[] cpids = childpids;
final long[] stimes = starttimes;
return IntStream.range(0, size).mapToObj(i -> new ProcessHandleImpl(cpids[i], stimes[i]));
}
@Override
public Stream<ProcessHandle> descendants() {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission("manageProcess"));
}
int size = 100;
long[] pids = null;
long[] ppids = null;
long[] starttimes = null;
while (pids == null || size > pids.length) {
pids = new long[size];
ppids = new long[size];
starttimes = new long[size];
size = getProcessPids0(0, pids, ppids, starttimes);
}
int next = 0; // index of next process to check
int count = -1; // count of subprocesses scanned
long ppid = pid; // start looking for this parent
long ppStart = 0;
// Find the start time of the parent
for (int i = 0; i < size; i++) {
if (pids[i] == ppid) {
ppStart = starttimes[i];
break;
}
}
do {
// Scan from next to size looking for ppid with child start time
// the same or later than the parent.
// If found, exchange it with index next
for (int i = next; i < size; i++) {
if (ppids[i] == ppid &&
ppStart <= starttimes[i]) {
swap(pids, i, next);
swap(ppids, i, next);
swap(starttimes, i, next);
next++;
}
}
ppid = pids[++count]; // pick up the next pid to scan for
ppStart = starttimes[count]; // and its start time
} while (count < next);
final long[] cpids = pids;
final long[] stimes = starttimes;
return IntStream.range(0, count).mapToObj(i -> new ProcessHandleImpl(cpids[i], stimes[i]));
}
// 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, startTime);
}
@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) {
if (this == obj) {
return true;
}
if (obj instanceof ProcessHandleImpl) {
ProcessHandleImpl other = (ProcessHandleImpl) obj;
return (pid == other.pid) &&
(startTime == other.startTime
|| startTime == 0
|| other.startTime == 0);
}
return false;
}
/**
* Implementation of ProcessHandle.Info.
* Information snapshot about a process.
* The attributes of a process vary by operating system and are 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 commandLine;
String[] arguments;
long startTime;
long totalTime;
String user;
Info() {
command = null;
commandLine = 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.
* If the startTime of the process does not match the provided
* startTime then an empty Info is returned.
*
* @param pid the native process identifier
* @param startTime the startTime of the process being queried
* @return ProcessHandle.Info non-null; individual fields may be null
* or -1 if not available.
*/
public static ProcessHandle.Info info(long pid, long startTime) {
Info info = new Info();
info.info0(pid);
if (startTime != info.startTime) {
info.command = null;
info.arguments = null;
info.startTime = -1L;
info.totalTime = -1L;
info.user = null;
}
return info;
}
@Override
public Optional<String> command() {
return Optional.ofNullable(command);
}
@Override
public Optional<String> commandLine() {
if (command != null && arguments != null) {
return Optional.of(command + " " + String.join(" ", arguments));
} else {
return Optional.ofNullable(commandLine);
}
}
@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 (commandLine != null) {
if (sb.length() != 0) sb.append(", ");
sb.append("cmdLine: ");
sb.append(commandLine);
}
if (startTime > 0) {
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();
}
}
}