68110b7a82
Reviewed-by: bpb, naoto, jpai
484 lines
20 KiB
Java
484 lines
20 KiB
Java
/*
|
|
* Copyright (c) 2014, 2023, 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.testng.Assert;
|
|
import org.testng.TestNG;
|
|
import org.testng.annotations.Test;
|
|
|
|
/*
|
|
* @test
|
|
* @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 testng/othervm TreeTest
|
|
* @summary Test counting and JavaChild.spawning and counting of Processes.
|
|
* @author Roger Riggs
|
|
*/
|
|
public class TreeTest extends ProcessUtil {
|
|
// Main can be used to run the tests from the command line with only testng.jar.
|
|
@SuppressWarnings("raw_types")
|
|
public static void main(String[] args) {
|
|
Class<?>[] testclass = {TreeTest.class};
|
|
TestNG testng = new TestNG();
|
|
testng.setTestClasses(testclass);
|
|
testng.run();
|
|
}
|
|
|
|
/**
|
|
* Test counting and spawning and counting of Processes.
|
|
*/
|
|
@Test
|
|
public static void test1() {
|
|
final int MAXCHILDREN = 2;
|
|
List<JavaChild> spawned = new ArrayList<>();
|
|
|
|
try {
|
|
ProcessHandle self = ProcessHandle.current();
|
|
|
|
printf("self pid: %d%n", self.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 -> Assert.fail("Spawned process missing from children: " + p));
|
|
|
|
// Send exit command to each spawned Process
|
|
spawned.forEach(p -> {
|
|
try {
|
|
p.sendAction("exit", "");
|
|
} catch (IOException ex) {
|
|
Assert.fail("IOException in sendAction", ex);
|
|
}
|
|
});
|
|
|
|
// Wait for each Process to exit
|
|
spawned.forEach(p -> {
|
|
do {
|
|
try {
|
|
Assert.assertEquals(p.waitFor(), 0, "exit status incorrect");
|
|
break;
|
|
} catch (InterruptedException ex) {
|
|
continue; // Retry
|
|
}
|
|
} while (true);
|
|
});
|
|
|
|
// Verify that ProcessHandle.isAlive sees each of them as not alive
|
|
for (Process p : spawned) {
|
|
ProcessHandle ph = p.toHandle();
|
|
Assert.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(p -> afterChildren.contains(p))
|
|
.forEach(p -> Assert.fail("Spawned process missing from children: " + p));
|
|
|
|
} catch (IOException ioe) {
|
|
Assert.fail("unable to spawn process", ioe);
|
|
} finally {
|
|
// Cleanup any left over processes
|
|
spawned.stream()
|
|
.map(Process::toHandle)
|
|
.filter(ProcessHandle::isAlive)
|
|
.forEach(ph -> {
|
|
printDeep(ph, "test1 cleanup: ");
|
|
ph.destroyForcibly();
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Test counting and spawning and counting of Processes.
|
|
*/
|
|
@Test
|
|
public static 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 spawing process
|
|
p1.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());
|
|
}
|
|
});
|
|
|
|
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) {
|
|
Assert.assertTrue(ph.isAlive(), "Child should be alive: " + ph);
|
|
// Verify each child was started after the parent
|
|
ph.info().startInstant()
|
|
.ifPresent(childStart -> p1Start.ifPresent(parentStart -> {
|
|
Assert.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) -> {
|
|
Assert.assertEquals(p.isAlive(), true, "Child should be alive: " + p);
|
|
Assert.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) {
|
|
Assert.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) -> Assert.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 static 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.valueOf(split[2]);
|
|
Long parent = Long.valueOf(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
|
|
Assert.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();
|
|
Assert.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) -> {
|
|
Assert.assertEquals(p.isAlive(), true, "Child should be alive: " + p);
|
|
Assert.assertTrue(descendants.contains(p), "Spawned child should be listed in descendants: " + p);
|
|
p.destroyForcibly();
|
|
});
|
|
Assert.assertEquals(processes.size(), newChildren, "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());
|
|
Assert.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);
|
|
Assert.assertFalse(remaining.remove(p1Handle), "Child p1 should have exited");
|
|
remaining = remaining.stream().filter(processes::containsKey).collect(Collectors.toList());
|
|
Assert.assertEquals(remaining.size(), 0, "Subprocess(es) should have exited: " + remaining);
|
|
|
|
} catch (IOException ioe) {
|
|
Assert.fail("Spawn of subprocess failed", ioe);
|
|
} catch (InterruptedException inte) {
|
|
Assert.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 static void test4() {
|
|
printf(" Parent Child Info%n");
|
|
Stream<ProcessHandle> s = ProcessHandle.allProcesses();
|
|
ProcessHandle[] processes = s.toArray(ProcessHandle[]::new);
|
|
int len = processes.length;
|
|
ProcessHandle[] parent = new ProcessHandle[len];
|
|
Set<ProcessHandle> processesSet =
|
|
Arrays.stream(processes).collect(Collectors.toSet());
|
|
Integer[] sortindex = new Integer[len];
|
|
for (int i = 0; i < len; i++) {
|
|
sortindex[i] = i;
|
|
}
|
|
for (int i = 0; i < len; i++) {
|
|
parent[sortindex[i]] = processes[sortindex[i]].parent().orElse(null);
|
|
}
|
|
Arrays.sort(sortindex, (i1, i2) -> {
|
|
int cmp = Long.compare((parent[i1] == null ? 0L : parent[i1].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);
|
|
}
|
|
Assert.assertFalse(fail, "Parents missing from all Processes");
|
|
|
|
}
|
|
|
|
/**
|
|
* A test for scale; launch a large number (14) of subprocesses.
|
|
*/
|
|
@Test
|
|
public static 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.valueOf(split[2]);
|
|
Long parent = Long.valueOf(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
|
|
Assert.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();
|
|
Assert.assertEquals(0, count, "Extra processes in descendants");
|
|
}
|
|
|
|
List<ProcessHandle> subprocesses = getChildren(p1Handle);
|
|
printf(" children: %s%n",
|
|
subprocesses.stream().map(p -> p.pid())
|
|
.collect(Collectors.toList()));
|
|
|
|
Assert.assertEquals(getChildren(p1Handle).size(),
|
|
factor, "expected direct children");
|
|
count = getDescendants(p1Handle).size();
|
|
long totalChildren = factor * factor * factor + factor * factor + factor;
|
|
Assert.assertTrue(count >= totalChildren,
|
|
"expected at least " + totalChildren + ", actual: " + count);
|
|
|
|
List<ProcessHandle> descSubprocesses = getDescendants(p1Handle);
|
|
printf(" descendants: %s%n",
|
|
descSubprocesses.stream().map(p -> p.pid())
|
|
.collect(Collectors.toList()));
|
|
|
|
p1.getOutputStream().close(); // Close stdin for the controlling p1
|
|
p1.waitFor();
|
|
} catch (InterruptedException | IOException ex) {
|
|
Assert.fail("Unexpected Exception", ex);
|
|
} finally {
|
|
printf("Duration: %s%n", Duration.between(start, Instant.now()));
|
|
if (p1 != null) {
|
|
p1.destroyForcibly();
|
|
}
|
|
processes.forEach((p, parent) -> {
|
|
if (p.isAlive()) {
|
|
ProcessUtil.printProcess(p, "Process Cleanup: ");
|
|
p.destroyForcibly();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
}
|