undefect. CWE-407 — 63 sites patched across 27 ecosystems

Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
This commit is contained in:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,146 @@
/*
* Copyright (c) 2014, 2026, 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.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/*
* @test
* @summary Basic tests for ProcessHandler
* @library /test/lib
* @modules java.base/jdk.internal.misc
* jdk.management
* @build jdk.test.lib.Utils
* jdk.test.lib.Asserts
* jdk.test.lib.JDKToolFinder
* jdk.test.lib.JDKToolLauncher
* jdk.test.lib.Platform
* jdk.test.lib.process.*
* @run junit Basic
*/
public class Basic {
/**
* Tests of ProcessHandle.current.
*/
@Test
public void test1() {
try {
ProcessHandle self = ProcessHandle.current();
ProcessHandle self1 = ProcessHandle.current();
assertEquals(self1, self); //, "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 void test2() {
try {
ProcessHandle self = ProcessHandle.current();
long pid = self.pid(); // known native process id
Optional<ProcessHandle> self1 = ProcessHandle.of(pid);
assertEquals(self, self1.get(),
"ProcessHandle.of(x.pid()) should be equal pid() %d: %d");
Optional<ProcessHandle> ph = ProcessHandle.of(pid);
assertEquals(ph.get().pid(), pid);
} finally {
// Cleanup any left over processes
ProcessHandle.current().children().forEach(ProcessHandle::destroy);
}
}
@Test
public 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 void test4() {
try {
Process p = new ProcessBuilder("sleep", "0").start();
p.waitFor();
long deadPid = p.pid();
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 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
public void test6() {
Assertions.assertThrows(IllegalStateException.class, () -> {
ProcessHandle.current().onExit();
});
}
@Test
public void test7() {
Assertions.assertThrows(IllegalStateException.class, () -> {
ProcessHandle.current().destroyForcibly();
});
}
}

View file

@ -0,0 +1,458 @@
/*
* Copyright (c) 2014, 2026, 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.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.UserPrincipal;
import java.time.Duration;
import java.time.Instant;
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.concurrent.TimeUnit;
import jdk.test.lib.Platform;
import jdk.test.lib.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/*
* @test
* @summary Functions of ProcessHandle.Info
* @bug 8077350 8081566 8081567 8098852 8136597
* @requires os.arch != "riscv64" | !(vm.cpu.features ~= ".*qemu.*")
* @library /test/lib
* @modules java.base/jdk.internal.misc
* jdk.management
* @build jdk.test.lib.Utils
* jdk.test.lib.Asserts
* jdk.test.lib.JDKToolFinder
* jdk.test.lib.JDKToolLauncher
* jdk.test.lib.Platform
* jdk.test.lib.process.*
* @run junit InfoTest
* @author Roger Riggs
*/
public class InfoTest {
static String whoami;
static {
try {
// Create a file and take the username from the file
Path p = Paths.get("OwnerName.tmp");
Files.createFile(p);
UserPrincipal owner = Files.getOwner(p);
whoami = owner.getName();
Files.delete(p);
} catch (IOException ex) {
ex.printStackTrace();
throw new UncheckedIOException("tmp file", ex);
}
}
/**
* Test that cputime used shows up in ProcessHandle.info
*/
@Test
public 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)) {
Assertions.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 void test2() {
try {
long cpuLoopTime = 100; // 100 ms
String[] extraArgs = {"pid", "parent", "stdin"};
JavaChild p1 = JavaChild.spawnJavaChild((Object[])extraArgs);
Instant afterStart = null;
try (BufferedReader lines = p1.outputReader()) {
// Read the args line to know the subprocess has started
lines.readLine();
afterStart = Instant.now();
Duration lastCpu = Duration.ofMillis(0L);
for (int j = 0; j < 10; 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
for (String s; (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
}
}
if (Platform.isAix()) {
// Unfortunately, on AIX the usr/sys times reported through
// /proc/<pid>/psinfo which are used by ProcessHandle.Info
// are running slow compared to the corresponding times reported
// by the times()/getrusage() system calls which are used by
// OperatingSystemMXBean.getProcessCpuTime() and returned by
// the JavaChild for the "cputime" command.
// This is because /proc/<pid>/status is only updated once a second.
// So we better wait a little bit to get plausible values here.
Thread.sleep(1000);
}
ProcessHandle.Info info = p1.info();
System.out.printf(" info: %s%n", info);
assertUser(info);
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;
Path expectedPath = Paths.get(expected);
Path actualPath = Paths.get(command.get());
Assertions.assertTrue(Files.isSameFile(expectedPath, actualPath),
"Command: expected: " + javaExe + ", actual: " + command.get());
}
if (info.arguments().isPresent()) {
String[] args = info.arguments().get();
int offset = args.length - extraArgs.length;
for (int i = 0; i < extraArgs.length; i++) {
Assertions.assertEquals(extraArgs[i], args[offset + i],
"Actual argument mismatch, index: " + i);
}
// Now check that the first argument is not the same as the executed command
Assertions.assertNotEquals(command, args[0],
"First argument should not be the executable: args[0]: "
+ args[0] + ", command: " + command);
}
if (command.isPresent() && info.arguments().isPresent()) {
// If both, 'command' and 'arguments' are present,
// 'commandLine' is just the concatenation of the two.
Assertions.assertTrue(info.commandLine().isPresent(),
"commandLine() must be available");
String javaExe = System.getProperty("test.jdk") +
File.separator + "bin" + File.separator + "java";
String expected = Platform.isWindows() ? javaExe + ".exe" : javaExe;
Path expectedPath = Paths.get(expected);
String commandLine = info.commandLine().get();
String commandLineCmd = commandLine.split(" ")[0];
Path commandLineCmdPath = Paths.get(commandLineCmd);
Assertions.assertTrue(Files.isSameFile(commandLineCmdPath, expectedPath),
"commandLine() should start with: " + expectedPath +
" but starts with " + commandLineCmdPath);
Assertions.assertTrue(commandLine.contains(command.get()),
"commandLine() must contain the command: " + command.get());
List<String> allArgs = p1.getArgs();
for (int i = 1; i < allArgs.size(); i++) {
Assertions.assertTrue(commandLine.contains(allArgs.get(i)),
"commandLine() must contain argument: " + allArgs.get(i));
}
} else if (info.commandLine().isPresent() &&
command.isPresent() &&
command.get().length() < info.commandLine().get().length()) {
// If we only have the commandLine() we can only do some basic checks...
String commandLine = info.commandLine().get();
String javaExe = "java" + (Platform.isWindows() ? ".exe" : "");
int pos = commandLine.indexOf(javaExe);
Assertions.assertTrue(pos > 0, "commandLine() should at least contain 'java'");
pos += javaExe.length() + 1; // +1 for the space after the command
List<String> allArgs = p1.getArgs();
// First argument is the command - skip it here as we've already checked that.
for (int i = 1; (i < allArgs.size()) &&
(pos + allArgs.get(i).length() < commandLine.length()); i++) {
Assertions.assertTrue(commandLine.contains(allArgs.get(i)),
"commandLine() must contain argument: " + allArgs.get(i));
pos += allArgs.get(i).length() + 1;
}
}
if (info.totalCpuDuration().isPresent()) {
Duration totalCPU = info.totalCpuDuration().get();
Duration epsilon = Duration.ofMillis(200L);
if (childCpuTime != null) {
System.out.printf(" info.totalCPU: %s, childCpuTime: %s, diff: %s%n",
totalCPU.toNanos(), childCpuTime.toNanos(),
childCpuTime.toNanos() - totalCPU.toNanos());
Assertions.assertTrue(checkEpsilon(childCpuTime, totalCPU, epsilon),
childCpuTime + " should be within " +
epsilon + " of " + totalCPU);
}
Assertions.assertTrue(totalCPU.toNanos() > 0L,
"total cpu time expected > 0ms, actual: " + totalCPU);
long t = Utils.adjustTimeout(10L); // Adjusted timeout seconds
Assertions.assertTrue(totalCPU.toNanos() < lastCpu.toNanos() + t * 1_000_000_000L,
"total cpu time expected < " + t
+ " seconds more than previous iteration, actual: "
+ (totalCPU.toNanos() - lastCpu.toNanos()));
lastCpu = totalCPU;
}
if (info.startInstant().isPresent()) {
Instant startTime = info.startInstant().get();
Assertions.assertTrue(startTime.isBefore(afterStart),
"startTime after process spawn completed"
+ startTime + " + > " + afterStart);
}
}
}
p1.sendAction("exit");
Assertions.assertTrue(p1.waitFor(Utils.adjustTimeout(30L), TimeUnit.SECONDS),
"timeout waiting for process to terminate");
} catch (IOException | InterruptedException ie) {
ie.printStackTrace(System.out);
Assertions.fail("unexpected exception", ie);
} finally {
// Destroy any children that still exist
ProcessUtil.destroyProcessTree(ProcessHandle.current());
}
}
/**
* Spawn a child with arguments and check they are visible via the ProcessHandle.
*/
@Test
public void test3() {
try {
for (long sleepTime : Arrays.asList(Utils.adjustTimeout(30), Utils.adjustTimeout(32))) {
Process p = spawn("sleep", String.valueOf(sleepTime));
ProcessHandle.Info info = p.info();
System.out.printf(" info: %s%n", info);
assertUser(info);
if (info.command().isPresent()) {
String command = info.command().get();
String expected = "sleep";
if (Platform.isWindows()) {
expected = "sleep.exe";
} else if (Files.isSymbolicLink(Paths.get("/bin/sleep"))) {
// Busybox sleep is a symbolic link to /bin/busybox.
// Rust coreutils sleep is a symbolic link to coreutils
// The busbox/coreutils executables are seen as ProcessHandle.Info command.
Path executable = Files.readSymbolicLink(Paths.get("/bin/sleep"));
expected = executable.getFileName().toString();
}
Assertions.assertTrue(command.endsWith(expected), "Command: expected: \'" +
expected + "\', actual: " + command);
// Verify the command exists and is executable
File exe = new File(command);
Assertions.assertTrue(exe.exists(), "command must exist: " + exe);
Assertions.assertTrue(exe.canExecute(), "command must be executable: " + exe);
}
if (info.arguments().isPresent()) {
String[] args = info.arguments().get();
if (args.length > 0) {
Assertions.assertEquals(String.valueOf(sleepTime), args[0]);
}
}
p.destroy();
Assertions.assertTrue(p.waitFor(Utils.adjustTimeout(30), TimeUnit.SECONDS),
"timeout waiting for process to terminate");
}
} 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 void test4() {
Duration myCputime1 = ProcessUtil.MXBeanCpuTime();
if (Platform.isAix()) {
// Unfortunately, on AIX the usr/sys times reported through
// /proc/<pid>/psinfo which are used by ProcessHandle.Info
// are running slow compared to the corresponding times reported
// by the times()/getrusage() system calls which are used by
// OperatingSystemMXBean.getProcessCpuTime() and returned by
// the JavaChild for the "cputime" command.
// So we better wait a little bit to get plausible values here.
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {}
}
Optional<Duration> dur1 = ProcessHandle.current().info().totalCpuDuration();
Duration myCputime2 = ProcessUtil.MXBeanCpuTime();
if (Platform.isAix()) {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {}
}
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.
Assertions.assertTrue(checkEpsilon(myCputime1, myCputime2, epsilon),
myCputime1.toNanos() + " should be within " + epsilon
+ " of " + myCputime2.toNanos());
Assertions.assertTrue(checkEpsilon(total1, total2, epsilon),
total1.toNanos() + " should be within " + epsilon
+ " of " + total2.toNanos());
Assertions.assertTrue(checkEpsilon(myCputime1, total1, epsilon),
myCputime1.toNanos() + " should be within " + epsilon
+ " of " + total1.toNanos());
Assertions.assertTrue(checkEpsilon(total1, myCputime2, epsilon),
total1.toNanos() + " should be within " + epsilon
+ " of " + myCputime2.toNanos());
Assertions.assertTrue(checkEpsilon(myCputime2, total2, epsilon),
myCputime2.toNanos() + " should be within " + epsilon
+ " of " + total2.toNanos());
}
}
@Test
public 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
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 true if d2 is greater than d1 or within epsilon, false otherwise
*/
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
* @param args to start a new process
* @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();
}
/**
* Asserts the expected process user.
*
* The Expected user is determined by creating a file and reading its owner, see static block above.
*
* On Windows, when run privileged as member of the Administrators group, this does not always
* work because new files can be owned by BUILTIN\Administrators instead, depending on system
* settings. In that case we resort to comparing System property user.name, which does not contain
* the domain name, though.
*
* @param info ProcessHanlde info object
*/
static void assertUser(ProcessHandle.Info info) {
if (!info.user().isPresent()) {
return;
}
String user = info.user().get();
Assertions.assertNotNull(user, "User name");
if (Platform.isWindows() && "BUILTIN\\Administrators".equals(whoami)) {
int bsi = user.lastIndexOf("\\");
Assertions.assertEquals(System.getProperty("user.name"), bsi == -1 ? user : user.substring(bsi + 1), "User name");
} else {
Assertions.assertEquals(whoami, user, "User name");
}
}
}

View file

@ -0,0 +1,550 @@
/*
* 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.
*
* 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.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.concurrent.ExecutionException;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Optional;
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(ProcessBuilder pb) throws IOException {
allArgs = pb.command();
delegate = pb.start();
// 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 List<String> getArgs() {
return allArgs;
}
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-" + pid();
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 (" + pid() + "):" + 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);
}
/**
* 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");
// Will hold the complete list of arguments which was given to Processbuilder.command()
private List<String> allArgs;
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 cpuMillis = Long.valueOf(args[nextArg++]);
long cpuTarget = getCpuTime() + cpuMillis * 1_000_000L;
while (getCpuTime() < cpuTarget) {
// burn the cpu until the time is up
}
sendResult(action, cpuMillis);
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.pid(),
"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.pid());
System.err.printf(" Process not terminated: " +
"pid: %d%n", c.pid());
}
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.pid());
p.forEachOutputLine(JavaChild::sendRaw);
p.onJavaChildExit().thenAccept((p1) -> {
int excode = p1.exitValue();
sendResult("child_exit", p1.pid(), 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.pid());
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.pid());
p.getOutputStream().close();
}
}
break;
case "property":
String name = args[nextArg++];
sendResult(action, name, System.getProperty(name));
break;
case "threaddump":
Thread.dumpStack();
break;
case "waitpid":
long pid = Long.parseLong(args[nextArg++]);
Optional<String> s = ProcessHandle.of(pid).map(ph -> waitAlive(ph));
sendResult(action, s.orElse("pid not valid: " + pid));
break;
default:
throw new Error("JavaChild action unknown: " + action);
}
}
} catch (Throwable t) {
t.printStackTrace(System.err);
System.exit(1);
}
}
private static String waitAlive(ProcessHandle ph) {
String status;
try {
boolean isAlive = ph.onExit().get().isAlive();
status = Boolean.toString(isAlive);
} catch (InterruptedException | ExecutionException ex ) {
status = "interrupted";
}
return status;
}
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(" waitpid <pid> - wait for the pid to exit");
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.pid(), ++commandSeq, command, results);
}
Event(long pid, int seq, String command, Object... results) {
this.pid = pid;
this.seq = seq;
this.command = command;
this.results = results;
}
/**
* Create a String encoding the pid, seq, command, and results.
*
* @return a String formatted to send to the stream.
*/
String format() {
StringBuilder sb = new StringBuilder();
sb.append(pid);
sb.append(":");
sb.append(seq);
sb.append(" ");
sb.append(command);
for (int i = 0; i < results.length; i++) {
sb.append(" ");
sb.append(results[i]);
}
return sb.toString();
}
Event(String encoded) {
String[] split = encoded.split("\\s");
String[] pidSeq = split[0].split(":");
pid = Long.valueOf(pidSeq[0]);
seq = Integer.valueOf(pidSeq[1]);
command = split[1];
Arrays.copyOfRange(split, 1, split.length);
}
public String toString() {
return format();
}
}
}

View file

@ -0,0 +1,326 @@
/*
* Copyright (c) 2014, 2026, 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.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
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.concurrent.TimeUnit;
import jdk.test.lib.Platform;
import jdk.test.lib.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/*
* @test
* @summary Functions of Process.onExit and ProcessHandle.onExit
* @bug 8333742
* @requires vm.flagless
* @library /test/lib
* @modules jdk.management
* @build jdk.test.lib.Utils
* @run junit OnExitTest
*/
public class OnExitTest extends ProcessUtil {
/**
* Basic test of exitValue and onExit.
*/
@Test
public void test1() {
try {
int[] exitValues = {0, 1, 10, 259};
for (int value : exitValues) {
// Linux & Mac don't support exit codes longer than 8 bits, skip
if (value == 259 && !Platform.isWindows()) continue;
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);
Assertions.assertEquals(value, actualExitValue, "actualExitValue incorrect");
Assertions.assertEquals(p, ph, "Different Process passed to thenAccept");
});
Process h = future.get();
Assertions.assertEquals(p, h);
Assertions.assertEquals(value, p.exitValue());
Assertions.assertFalse(p.isAlive(), "Process should not be alive");
Assertions.assertEquals(value, p.waitFor());
Assertions.assertTrue(p.waitFor(1, TimeUnit.MILLISECONDS),
"waitFor should return true");
}
} catch (IOException | InterruptedException | ExecutionException ex) {
Assertions.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 void test2() {
// Please note (JDK-8284282):
//
// On Unix, this test relies on the ability of the system to adopt orphaned processes and
// reap them in a timely fashion. In other words, the ability to prevent orphans from becoming
// zombies.
//
// Therefore, on misconfigured or broken systems, this test may fail. These failures will manifest
// as timeouts. The failures depend on timing: they may not happen at all, be intermittent or
// constant.
//
// That will rarely be a problem on bare-metal systems but may be more common when running in
// Docker. Misconfigured Docker instances may run with an initial process unable to reap. One
// infamous example would be running jtreg tests inside a Docker via Jenkins CI.
//
// This is quite difficult - and inefficient - to fix inside this test, and rather easy to
// avoid. For a detailed analysis, as well as proposed workarounds, please see JDK-8284282.
//
ProcessHandle procHandle = null;
try {
ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();
List<ProcessHandle> children = getChildren(ProcessHandle.current());
children.forEach(ProcessUtil::printProcess);
JavaChild proc = JavaChild.spawnJavaChild("stdin");
procHandle = proc.toHandle();
printf(" spawned: %d%n", proc.pid());
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;
long timeout = Utils.adjustTimeout(10L);
Instant endTimeout = Instant.now().plusSeconds(timeout);
do {
Thread.sleep(200L);
printf(" subprocess count: %d, waiting for %d%n", processes.size(), expected);
} while (processes.size() < expected &&
Instant.now().isBefore(endTimeout));
if (processes.size() < expected) {
printf("WARNING: not all children have been started. Can't complete test.%n");
printf(" You can try to increase the timeout or%n");
printf(" you can try to use a faster VM (i.e. not a debug version).%n");
}
children = getDescendants(procHandle);
ConcurrentHashMap<ProcessHandle, CompletableFuture<ProcessHandle>> completions =
new ConcurrentHashMap<>();
Instant startTime = Instant.now();
// Create a future for each of the 9 children
processes.forEach( (p, parent) -> {
CompletableFuture<ProcessHandle> cf = p.onExit().whenComplete((ph, ex) -> {
Duration elapsed = Duration.between(startTime, Instant.now());
printf("whenComplete: pid: %s, exception: %s, thread: %s, elapsed: %s%n",
ph, ex, Thread.currentThread(), elapsed);
});
completions.put(p, cf);
});
// Check that each of the spawned processes is included in the children
List<ProcessHandle> remaining = new ArrayList<>(children);
processes.forEach((p, parent) -> {
Assertions.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 and corresponding onExit CF to be completed
processes.forEach((p, parent) -> {
try {
p.onExit().get();
completions.get(p).join();
} catch (InterruptedException | ExecutionException ex) {
// ignore
}
});
// Verify that all 9 exit handlers were called with the correct ProcessHandle
processes.forEach((p, parent) -> {
ProcessHandle value = completions.get(p).getNow(null);
Assertions.assertEquals(value, p, "onExit.get value expected: " + p
+ ", actual: " + value
+ ": " + p.info());
});
// Show the status of the original children
children.forEach(p -> printProcess(p, "after onExit:"));
Assertions.assertFalse(proc.isAlive(), "destroyed process is alive:: %s%n" + proc);
} catch (IOException | InterruptedException ex) {
Assertions.fail(ex.getMessage(), ex);
} finally {
if (procHandle != null) {
destroyProcessTree(procHandle);
}
}
}
/**
* Verify that onExit completes for a non-child process only when
* the process has exited.
* Spawn a child (A) waiting to be commanded to exit.
* Spawn a child (B) to wait for that process to exit.
* Command (A) to exit.
* Check that (B) does not complete until (A) has exited.
*/
@Test
public void peerOnExitTest() {
String line = null;
ArrayBlockingQueue<String> alines = new ArrayBlockingQueue<>(100);
ArrayBlockingQueue<String> blines = new ArrayBlockingQueue<>(100);
JavaChild A = null;
try {
String[] split;
A = JavaChild.spawnJavaChild("stdin");
A.forEachOutputLine(l -> alines.add(l));
// Verify A is running
A.sendAction("pid");
do {
split = getSplitLine(alines);
} while (!"pid".equals(split[1]));
JavaChild B = null;
try {
B = JavaChild.spawnJavaChild("stdin");
B.forEachOutputLine(l -> blines.add(l));
// Verify B is running
B.sendAction("pid");
do {
split = getSplitLine(blines);
} while (!"pid".equals(split[1]));
// Tell B to wait for A's pid
B.sendAction("waitpid", A.pid());
// Wait a bit to see if B will prematurely report the termination of A
try {
line = blines.poll(5L, TimeUnit.SECONDS);
} catch (InterruptedException ie) {
Assertions.fail("interrupted", ie);
}
Assertions.assertNull(line, "waitpid didn't wait");
A.toHandle().onExit().thenAccept(p -> {
System.out.printf(" A.toHandle().onExit().A info: %s, now: %s%n",
p.info(), Instant.now());
});
A.onExit().thenAccept(p -> {
System.out.printf(" A.onExit().A info: %s, now: %s%n",
p.info(), Instant.now());
});
ProcessHandle.Info A_info = A.info();
A.sendAction("exit", 0L);
// Look for B to report that A has exited
do {
Instant start = Instant.now();
while (blines.isEmpty() && A.isAlive()) {
A_info = A.info(); // Spin
}
Instant end = Instant.now();
System.out.printf(" a.isAlive: %s, a.info: %s, @%s%n", A.isAlive(), A.info(),
Duration.between(start, end));
split = getSplitLine(blines);
} while (!"waitpid".equals(split[1]));
Assertions.assertEquals("false", split[2], "Process A should not be alive");
B.sendAction("exit", 0L);
} catch (IOException ioe) {
Assertions.fail("unable to start JavaChild B", ioe);
} finally {
B.destroyForcibly();
}
} catch (IOException ioe2) {
Assertions.fail("unable to start JavaChild A", ioe2);
} finally {
A.destroyForcibly();
}
}
private static boolean DEBUG = true;
/**
* Get a line from the queue and split into words on whitespace.
* Log to stdout if requested.
* @param queue a queue of strings
* @return the words split from the line.
*/
private static String[] getSplitLine(ArrayBlockingQueue<String> queue) {
try {
String line = queue.take();
String[] split = line.split("\\s");
if (DEBUG) {
System.out.printf(" Child Output: %s%n", line);
}
return split;
} catch (InterruptedException ie) {
Assertions.fail("interrupted", ie);
return null;
}
}
}

View file

@ -0,0 +1,241 @@
/*
* Copyright (c) 2015, 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.
*
* 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.time.Duration;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.sun.management.OperatingSystemMXBean;
import jdk.test.lib.Platform;
/**
* 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> getDescendants(ProcessHandle ph) {
return ph.descendants()
.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 = getDescendants(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.descendants().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.pid(), ph.isAlive(), ph.parent(), ph.info());
}
/**
* Print the process hierarchy as visible via ProcessHandle.
* Prints the pid, if it is alive, and information about the process.
* @param ph the processHandle at the top
* @param prefix the String to prefix the output with
*/
static void printDeep(ProcessHandle ph, String prefix) {
printProcess(ph, prefix);
ph.children().forEach(p -> printDeep(p, prefix + " "));
}
/**
* Use the native command to list the active processes.
*/
static void logTaskList() {
String[] windowsArglist = {"tasklist.exe", "/v"};
String[] unixArglist = {"ps", "-ef"};
String[] argList = null;
if (Platform.isWindows()) {
argList = windowsArglist;
} else if (Platform.isLinux() || Platform.isOSX()) {
argList = unixArglist;
} else {
return;
}
ProcessBuilder pb = new ProcessBuilder(argList);
pb.inheritIO();
try {
Process proc = pb.start();
proc.waitFor();
} catch (IOException | InterruptedException ex) {
ex.printStackTrace();
}
}
}

View file

@ -0,0 +1 @@
maxOutputSize=6000000

View file

@ -0,0 +1,472 @@
/*
* Copyright (c) 2014, 2026, 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.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.test.lib.Utils;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
/*
* @test
* @summary Test counting and JavaChild.spawning and counting of Processes.
* @requires vm.flagless
* @library /test/lib
* @modules java.base/jdk.internal.misc
* jdk.management
* @build jdk.test.lib.Utils
* jdk.test.lib.Asserts
* jdk.test.lib.JDKToolFinder
* jdk.test.lib.JDKToolLauncher
* jdk.test.lib.Platform
* jdk.test.lib.process.*
* @run junit/othervm TreeTest
* @author Roger Riggs
*/
public class TreeTest extends ProcessUtil {
/**
* Test counting and spawning and counting of Processes.
*/
@Test
public void test1() {
final int MAXCHILDREN = 2;
List<JavaChild> spawned = new ArrayList<>();
try {
ProcessHandle self = ProcessHandle.current();
printf("self pid: %d%n", self.pid());
printDeep(self, "");
for (int i = 0; i < MAXCHILDREN; i++) {
// spawn and wait for instructions
spawned.add(JavaChild.spawnJavaChild("pid", "stdin"));
}
// Verify spawned Process is in list of children
final List<ProcessHandle> initialChildren = getChildren(self);
spawned.stream()
.map(Process::toHandle)
.filter(p -> !initialChildren.contains(p))
.forEach(p -> Assertions.fail("Spawned process missing from children: " + p));
// Send exit command to each spawned Process
spawned.forEach(p -> {
try {
p.sendAction("exit", "");
} catch (IOException ex) {
Assertions.fail("IOException in sendAction", ex);
}
});
// Wait for each Process to exit
spawned.forEach(p -> {
do {
try {
Assertions.assertEquals(0, p.waitFor(), "exit status incorrect");
break;
} catch (InterruptedException ex) {
continue; // Retry
}
} while (true);
});
// Verify that ProcessHandle.isAlive sees each of them as not alive
for (Process p : spawned) {
ProcessHandle ph = p.toHandle();
Assertions.assertFalse(ph.isAlive(),
"ProcessHandle.isAlive for exited process: " + ph);
}
// Verify spawned processes are not visible as children
final List<ProcessHandle> afterChildren = getChildren(self);
spawned.stream()
.map(Process::toHandle)
.filter(afterChildren::contains)
.forEach(p -> Assertions.fail("Spawned process missing from children: " + p));
} catch (IOException ioe) {
Assertions.fail("unable to spawn process", ioe);
} finally {
// Cleanup any leftover processes
spawned.stream()
.map(Process::toHandle)
.filter(ProcessHandle::isAlive)
.forEach(ph -> {
printDeep(ph, "test1 cleanup: ");
ph.destroyForcibly();
});
}
}
/**
* Test counting and spawning and counting of Processes.
*/
@Test
public void test2() {
try {
ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();
ProcessHandle self = ProcessHandle.current();
List<ProcessHandle> initialChildren = getChildren(self);
long count = initialChildren.size();
if (count > 0) {
initialChildren.forEach(p -> printDeep(p, "test2 initial unexpected: "));
}
JavaChild p1 = JavaChild.spawnJavaChild("stdin");
ProcessHandle p1Handle = p1.toHandle();
printf(" p1 pid: %d%n", p1.pid());
// Gather the PIDs from the output of the spawning process
p1.forEachOutputLine((s) -> {
String[] split = s.trim().split(" ");
if (split.length == 3 && split[1].equals("spawn")) {
long child = Long.parseLong(split[2]);
long parent = Long.parseLong(split[0].split(":")[0]);
processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get());
}
});
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);
Optional<Instant> p1Start = p1Handle.info().startInstant();
for (ProcessHandle ph : subprocesses) {
Assertions.assertTrue(ph.isAlive(), "Child should be alive: " + ph);
// Verify each child was started after the parent
ph.info().startInstant()
.ifPresent(childStart -> p1Start.ifPresent(parentStart -> {
Assertions.assertFalse(childStart.isBefore(parentStart),
String.format("Child process started before parent: child: %s, parent: %s",
childStart, parentStart));
}));
}
// Each child spawns two processes and waits for commands
int spawnNewSub = 2;
p1.sendAction("child", "spawn", spawnNewSub, "stdin");
// Poll until all 9 child processes exist or the timeout is reached
int expected = 9;
long timeout = Utils.adjustTimeout(60L);
Instant endTimeout = Instant.now().plusSeconds(timeout);
do {
Thread.sleep(200L);
printf(" subprocess count: %d, waiting for %d%n", processes.size(), expected);
} while (processes.size() < expected &&
Instant.now().isBefore(endTimeout));
if (processes.size() < expected) {
printf("WARNING: not all children have been started. Can't complete test.%n");
printf(" You can try to increase the timeout or%n");
printf(" you can try to use a faster VM (i.e. not a debug version).%n");
}
// show the complete list of children (for debug)
List<ProcessHandle> descendants = getDescendants(p1Handle);
printf(" descendants: %s%n",
descendants.stream().map(p -> p.pid())
.collect(Collectors.toList()));
// Verify that all spawned children show up in the descendants List
processes.forEach((p, parent) -> {
Assertions.assertTrue(p.isAlive(), "Child should be alive: " + p);
Assertions.assertTrue(descendants.contains(p), "Spawned child should be listed in descendants: " + p);
});
// Closing JavaChild's InputStream will cause all children to exit
p1.getOutputStream().close();
for (ProcessHandle p : descendants) {
try {
p.onExit().get(); // wait for the child to exit
} catch (ExecutionException e) {
Assertions.fail("waiting for process to exit", e);
}
}
p1.waitFor(); // wait for spawned process to exit
// Verify spawned processes are no longer alive
processes.forEach((ph, parent) -> Assertions.assertFalse(ph.isAlive(),
"process should not be alive: " + ph));
} catch (IOException | InterruptedException t) {
t.printStackTrace();
throw new RuntimeException(t);
}
}
/**
* Test destroy of processes.
* A JavaChild is started and it starts three children.
* Each one is then checked to be alive and listed by descendants
* and forcibly destroyed.
* After they exit they should no longer be listed by descendants.
*/
@Test
public void test3() {
ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();
try {
ProcessHandle self = ProcessHandle.current();
JavaChild p1 = JavaChild.spawnJavaChild("stdin");
ProcessHandle p1Handle = p1.toHandle();
printf(" p1: %s%n", p1.pid());
int newChildren = 3;
CountDownLatch spawnCount = new CountDownLatch(newChildren);
// Spawn children and have them wait
p1.sendAction("spawn", newChildren, "stdin");
// Gather the PIDs from the output of the spawning process
p1.forEachOutputLine((s) -> {
String[] split = s.trim().split(" ");
if (split.length == 3 && split[1].equals("spawn")) {
long child = Long.parseLong(split[2]);
long parent = Long.parseLong(split[0].split(":")[0]);
processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get());
spawnCount.countDown();
}
});
// Wait for all the subprocesses to be listed as started
Assertions.assertTrue(spawnCount.await(Utils.adjustTimeout(30L), TimeUnit.SECONDS),
"Timeout waiting for processes to start");
// Debugging; list descendants that are not expected in processes
List<ProcessHandle> descendants = ProcessUtil.getDescendants(p1Handle);
long count = descendants.stream()
.filter(ph -> !processes.containsKey(ph))
.count();
if (count > 0) {
descendants.stream()
.filter(ph -> !processes.containsKey(ph))
.forEach(ph1 -> ProcessUtil.printProcess(ph1, "Extra process: "));
ProcessUtil.logTaskList();
Assertions.assertEquals(0, count, "Extra processes in descendants");
}
// Verify that all spawned children are alive, show up in the descendants list
// then destroy them
processes.forEach((p, parent) -> {
Assertions.assertTrue(p.isAlive(), "Child should be alive: " + p);
Assertions.assertTrue(descendants.contains(p), "Spawned child should be listed in descendants: " + p);
p.destroyForcibly();
});
Assertions.assertEquals(newChildren, processes.size(), "Wrong number of children");
// Wait for each of the processes to exit
processes.forEach((p, parent) -> {
for (long retries = Utils.adjustTimeout(100L); retries > 0 ; retries--) {
if (!p.isAlive()) {
return; // not alive, go on to the next
}
// Wait a bit and retry
try {
Thread.sleep(100L);
} catch (InterruptedException ie) {
// try again
}
}
printf("Timeout waiting for exit of pid %s, parent: %s, info: %s%n",
p, parent, p.info());
Assertions.fail("Process still alive: " + p);
});
p1.destroyForcibly();
p1.waitFor();
// Verify that none of the spawned children are still listed by descendants
List<ProcessHandle> remaining = getDescendants(self);
Assertions.assertFalse(remaining.remove(p1Handle), "Child p1 should have exited");
remaining = remaining.stream().filter(processes::containsKey).collect(Collectors.toList());
Assertions.assertEquals(0, remaining.size(), "Subprocess(es) should have exited: " + remaining);
} catch (IOException ioe) {
Assertions.fail("Spawn of subprocess failed", ioe);
} catch (InterruptedException inte) {
Assertions.fail("InterruptedException", inte);
} finally {
processes.forEach((p, parent) -> {
if (p.isAlive()) {
ProcessUtil.printProcess(p);
p.destroyForcibly();
}
});
}
}
/**
* Test (Not really a test) that dumps the list of all Processes.
*/
@Test
public 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].pid()),
(parent[i2] == null ? 0L : parent[i2].pid()));
if (cmp == 0) {
cmp = Long.compare((processes[i1] == null ? 0L : processes[i1].pid()),
(processes[i2] == null ? 0L : processes[i2].pid()));
}
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);
}
Assertions.assertFalse(fail, "Parents missing from all Processes");
}
/**
* A test for scale; launch a large number (14) of subprocesses.
*/
@Test
public void test5() {
ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>();
int factor = 2;
JavaChild p1 = null;
Instant start = Instant.now();
try {
p1 = JavaChild.spawnJavaChild("stdin");
ProcessHandle p1Handle = p1.toHandle();
printf("Spawning %d x %d x %d processes, pid: %d%n",
factor, factor, factor, p1.pid());
// 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));
CountDownLatch spawnCount = new CountDownLatch(newChildren);
// Gather the PIDs from the output of the spawning process
p1.forEachOutputLine((s) -> {
String[] split = s.trim().split(" ");
if (split.length == 3 && split[1].equals("spawn")) {
long child = Long.parseLong(split[2]);
long parent = Long.parseLong(split[0].split(":")[0]);
processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get());
spawnCount.countDown();
}
});
// Wait for all the subprocesses to be listed as started
Assertions.assertTrue(spawnCount.await(Utils.adjustTimeout(30L), TimeUnit.SECONDS),
"Timeout waiting for processes to start");
// Debugging; list descendants that are not expected in processes
List<ProcessHandle> descendants = ProcessUtil.getDescendants(p1Handle);
long count = descendants.stream()
.filter(ph -> !processes.containsKey(ph))
.count();
if (count > 0) {
descendants.stream()
.filter(ph -> !processes.containsKey(ph))
.forEach(ph1 -> ProcessUtil.printProcess(ph1, "Extra process: "));
ProcessUtil.logTaskList();
Assertions.assertEquals(0, count, "Extra processes in descendants");
}
List<ProcessHandle> subprocesses = getChildren(p1Handle);
printf(" children: %s%n",
subprocesses.stream().map(ProcessHandle::pid)
.collect(Collectors.toList()));
Assertions.assertEquals(factor, getChildren(p1Handle).size(), "expected direct children");
count = getDescendants(p1Handle).size();
long totalChildren = factor * factor * factor + factor * factor + factor;
Assertions.assertTrue(count >= totalChildren,
"expected at least " + totalChildren + ", actual: " + count);
List<ProcessHandle> descSubprocesses = getDescendants(p1Handle);
printf(" descendants: %s%n",
descSubprocesses.stream().map(ProcessHandle::pid)
.collect(Collectors.toList()));
p1.getOutputStream().close(); // Close stdin for the controlling p1
p1.waitFor();
} catch (InterruptedException | IOException ex) {
Assertions.fail("Unexpected Exception", ex);
} finally {
printf("Duration: %s%n", Duration.between(start, Instant.now()));
if (p1 != null) {
p1.destroyForcibly();
}
processes.forEach((p, parent) -> {
if (p.isAlive()) {
ProcessUtil.printProcess(p, "Process Cleanup: ");
p.destroyForcibly();
}
});
}
}
}