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:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
146
test/jdk/java/lang/ProcessHandle/Basic.java
Normal file
146
test/jdk/java/lang/ProcessHandle/Basic.java
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
458
test/jdk/java/lang/ProcessHandle/InfoTest.java
Normal file
458
test/jdk/java/lang/ProcessHandle/InfoTest.java
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
550
test/jdk/java/lang/ProcessHandle/JavaChild.java
Normal file
550
test/jdk/java/lang/ProcessHandle/JavaChild.java
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
326
test/jdk/java/lang/ProcessHandle/OnExitTest.java
Normal file
326
test/jdk/java/lang/ProcessHandle/OnExitTest.java
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
241
test/jdk/java/lang/ProcessHandle/ProcessUtil.java
Normal file
241
test/jdk/java/lang/ProcessHandle/ProcessUtil.java
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
1
test/jdk/java/lang/ProcessHandle/TEST.properties
Normal file
1
test/jdk/java/lang/ProcessHandle/TEST.properties
Normal file
|
|
@ -0,0 +1 @@
|
|||
maxOutputSize=6000000
|
||||
472
test/jdk/java/lang/ProcessHandle/TreeTest.java
Normal file
472
test/jdk/java/lang/ProcessHandle/TreeTest.java
Normal 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue