1 /* 2 * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 import java.io.IOException; 26 import java.time.Duration; 27 import java.time.Instant; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.List; 31 import java.util.Optional; 32 import java.util.Set; 33 import java.util.concurrent.ConcurrentHashMap; 34 import java.util.concurrent.CountDownLatch; 35 import java.util.concurrent.ExecutionException; 36 import java.util.concurrent.TimeUnit; 37 import java.util.stream.Collectors; 38 import java.util.stream.Stream; 39 40 import jdk.test.lib.Utils; 41 import org.testng.Assert; 42 import org.testng.TestNG; 43 import org.testng.annotations.Test; 44 45 /* 46 * @test 47 * @library /test/lib 48 * @modules java.base/jdk.internal.misc 49 * jdk.management 50 * @build jdk.test.lib.Utils 51 * jdk.test.lib.Asserts 52 * jdk.test.lib.JDKToolFinder 53 * jdk.test.lib.JDKToolLauncher 54 * jdk.test.lib.Platform 55 * jdk.test.lib.process.* 56 * @run testng/othervm TreeTest 57 * @summary Test counting and JavaChild.spawning and counting of Processes. 58 * @author Roger Riggs 59 */ 60 public class TreeTest extends ProcessUtil { 61 // Main can be used to run the tests from the command line with only testng.jar. 62 @SuppressWarnings("raw_types") 63 public static void main(String[] args) { 64 Class<?>[] testclass = {TreeTest.class}; 65 TestNG testng = new TestNG(); 66 testng.setTestClasses(testclass); 67 testng.run(); 68 } 69 70 /** 71 * Test counting and spawning and counting of Processes. 72 */ 73 @Test 74 public static void test1() { 75 final int MAXCHILDREN = 2; 76 List<JavaChild> spawned = new ArrayList<>(); 77 78 try { 79 ProcessHandle self = ProcessHandle.current(); 80 81 printf("self pid: %d%n", self.pid()); 82 printDeep(self, ""); 83 84 for (int i = 0; i < MAXCHILDREN; i++) { 85 // spawn and wait for instructions 86 spawned.add(JavaChild.spawnJavaChild("pid", "stdin")); 87 } 88 89 // Verify spawned Process is in list of children 90 final List<ProcessHandle> initialChildren = getChildren(self); 91 spawned.stream() 92 .map(Process::toHandle) 93 .filter(p -> !initialChildren.contains(p)) 94 .forEach(p -> Assert.fail("Spawned process missing from children: " + p)); 95 96 // Send exit command to each spawned Process 97 spawned.forEach(p -> { 98 try { 99 p.sendAction("exit", ""); 100 } catch (IOException ex) { 101 Assert.fail("IOException in sendAction", ex); 102 } 103 }); 104 105 // Wait for each Process to exit 106 spawned.forEach(p -> { 107 do { 108 try { 109 Assert.assertEquals(p.waitFor(), 0, "exit status incorrect"); 110 break; 111 } catch (InterruptedException ex) { 112 continue; // Retry 113 } 114 } while (true); 115 }); 116 117 // Verify that ProcessHandle.isAlive sees each of them as not alive 118 for (Process p : spawned) { 119 ProcessHandle ph = p.toHandle(); 120 Assert.assertFalse(ph.isAlive(), 121 "ProcessHandle.isAlive for exited process: " + ph); 122 } 123 124 // Verify spawned processes are not visible as children 125 final List<ProcessHandle> afterChildren = getChildren(self); 126 spawned.stream() 127 .map(Process::toHandle) 128 .filter(p -> afterChildren.contains(p)) 129 .forEach(p -> Assert.fail("Spawned process missing from children: " + p)); 130 131 } catch (IOException ioe) { 132 Assert.fail("unable to spawn process", ioe); 133 } finally { 134 // Cleanup any left over processes 135 spawned.stream() 136 .map(Process::toHandle) 137 .filter(ProcessHandle::isAlive) 138 .forEach(ph -> { 139 printDeep(ph, "test1 cleanup: "); 140 ph.destroyForcibly(); 141 }); 142 } 143 } 144 145 /** 146 * Test counting and spawning and counting of Processes. 147 */ 148 @Test 149 public static void test2() { 150 try { 151 ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>(); 152 153 ProcessHandle self = ProcessHandle.current(); 154 List<ProcessHandle> initialChildren = getChildren(self); 155 long count = initialChildren.size(); 156 if (count > 0) { 157 initialChildren.forEach(p -> printDeep(p, "test2 initial unexpected: ")); 158 } 159 160 JavaChild p1 = JavaChild.spawnJavaChild("stdin"); 161 ProcessHandle p1Handle = p1.toHandle(); 162 printf(" p1 pid: %d%n", p1.pid()); 163 164 // Gather the PIDs from the output of the spawing process 165 p1.forEachOutputLine((s) -> { 166 String[] split = s.trim().split(" "); 167 if (split.length == 3 && split[1].equals("spawn")) { 168 Long child = Long.valueOf(split[2]); 169 Long parent = Long.valueOf(split[0].split(":")[0]); 170 processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get()); 171 } 172 }); 173 174 int spawnNew = 3; 175 p1.sendAction("spawn", spawnNew, "stdin"); 176 177 // Wait for direct children to be created and save the list 178 List<ProcessHandle> subprocesses = waitForAllChildren(p1Handle, spawnNew); 179 Optional<Instant> p1Start = p1Handle.info().startInstant(); 180 for (ProcessHandle ph : subprocesses) { 181 Assert.assertTrue(ph.isAlive(), "Child should be alive: " + ph); 182 // Verify each child was started after the parent 183 ph.info().startInstant() 184 .ifPresent(childStart -> p1Start.ifPresent(parentStart -> { 185 Assert.assertFalse(childStart.isBefore(parentStart), 186 String.format("Child process started before parent: child: %s, parent: %s", 187 childStart, parentStart)); 188 })); 189 } 190 191 // Each child spawns two processes and waits for commands 192 int spawnNewSub = 2; 193 p1.sendAction("child", "spawn", spawnNewSub, "stdin"); 194 195 // Poll until all 9 child processes exist or the timeout is reached 196 int expected = 9; 197 long timeout = Utils.adjustTimeout(60L); 198 Instant endTimeout = Instant.now().plusSeconds(timeout); 199 do { 200 Thread.sleep(200L); 201 printf(" subprocess count: %d, waiting for %d%n", processes.size(), expected); 202 } while (processes.size() < expected && 203 Instant.now().isBefore(endTimeout)); 204 205 if (processes.size() < expected) { 206 printf("WARNING: not all children have been started. Can't complete test.%n"); 207 printf(" You can try to increase the timeout or%n"); 208 printf(" you can try to use a faster VM (i.e. not a debug version).%n"); 209 } 210 211 // show the complete list of children (for debug) 212 List<ProcessHandle> descendants = getDescendants(p1Handle); 213 printf(" descendants: %s%n", 214 descendants.stream().map(p -> p.pid()) 215 .collect(Collectors.toList())); 216 217 // Verify that all spawned children show up in the descendants List 218 processes.forEach((p, parent) -> { 219 Assert.assertEquals(p.isAlive(), true, "Child should be alive: " + p); 220 Assert.assertTrue(descendants.contains(p), "Spawned child should be listed in descendants: " + p); 221 }); 222 223 // Closing JavaChild's InputStream will cause all children to exit 224 p1.getOutputStream().close(); 225 226 for (ProcessHandle p : descendants) { 227 try { 228 p.onExit().get(); // wait for the child to exit 229 } catch (ExecutionException e) { 230 Assert.fail("waiting for process to exit", e); 231 } 232 } 233 p1.waitFor(); // wait for spawned process to exit 234 235 // Verify spawned processes are no longer alive 236 processes.forEach((ph, parent) -> Assert.assertFalse(ph.isAlive(), 237 "process should not be alive: " + ph)); 238 } catch (IOException | InterruptedException t) { 239 t.printStackTrace(); 240 throw new RuntimeException(t); 241 } 242 } 243 244 /** 245 * Test destroy of processes. 246 * A JavaChild is started and it starts three children. 247 * Each one is then checked to be alive and listed by descendants 248 * and forcibly destroyed. 249 * After they exit they should no longer be listed by descendants. 250 */ 251 @Test 252 public static void test3() { 253 ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>(); 254 255 try { 256 ProcessHandle self = ProcessHandle.current(); 257 258 JavaChild p1 = JavaChild.spawnJavaChild("stdin"); 259 ProcessHandle p1Handle = p1.toHandle(); 260 printf(" p1: %s%n", p1.pid()); 261 262 int newChildren = 3; 263 CountDownLatch spawnCount = new CountDownLatch(newChildren); 264 // Spawn children and have them wait 265 p1.sendAction("spawn", newChildren, "stdin"); 266 267 // Gather the PIDs from the output of the spawning process 268 p1.forEachOutputLine((s) -> { 269 String[] split = s.trim().split(" "); 270 if (split.length == 3 && split[1].equals("spawn")) { 271 Long child = Long.valueOf(split[2]); 272 Long parent = Long.valueOf(split[0].split(":")[0]); 273 processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get()); 274 spawnCount.countDown(); 275 } 276 }); 277 278 // Wait for all the subprocesses to be listed as started 279 Assert.assertTrue(spawnCount.await(Utils.adjustTimeout(30L), TimeUnit.SECONDS), 280 "Timeout waiting for processes to start"); 281 282 // Debugging; list descendants that are not expected in processes 283 List<ProcessHandle> descendants = ProcessUtil.getDescendants(p1Handle); 284 long count = descendants.stream() 285 .filter(ph -> !processes.containsKey(ph)) 286 .count(); 287 if (count > 0) { 288 descendants.stream() 289 .filter(ph -> !processes.containsKey(ph)) 290 .forEach(ph1 -> ProcessUtil.printProcess(ph1, "Extra process: ")); 291 ProcessUtil.logTaskList(); 292 Assert.assertEquals(0, count, "Extra processes in descendants"); 293 } 294 295 // Verify that all spawned children are alive, show up in the descendants list 296 // then destroy them 297 processes.forEach((p, parent) -> { 298 Assert.assertEquals(p.isAlive(), true, "Child should be alive: " + p); 299 Assert.assertTrue(descendants.contains(p), "Spawned child should be listed in descendants: " + p); 300 p.destroyForcibly(); 301 }); 302 Assert.assertEquals(processes.size(), newChildren, "Wrong number of children"); 303 304 // Wait for each of the processes to exit 305 processes.forEach((p, parent) -> { 306 for (long retries = Utils.adjustTimeout(100L); retries > 0 ; retries--) { 307 if (!p.isAlive()) { 308 return; // not alive, go on to the next 309 } 310 // Wait a bit and retry 311 try { 312 Thread.sleep(100L); 313 } catch (InterruptedException ie) { 314 // try again 315 } 316 } 317 printf("Timeout waiting for exit of pid %s, parent: %s, info: %s%n", 318 p, parent, p.info()); 319 Assert.fail("Process still alive: " + p); 320 }); 321 p1.destroyForcibly(); 322 p1.waitFor(); 323 324 // Verify that none of the spawned children are still listed by descendants 325 List<ProcessHandle> remaining = getDescendants(self); 326 Assert.assertFalse(remaining.remove(p1Handle), "Child p1 should have exited"); 327 remaining = remaining.stream().filter(processes::containsKey).collect(Collectors.toList()); 328 Assert.assertEquals(remaining.size(), 0, "Subprocess(es) should have exited: " + remaining); 329 330 } catch (IOException ioe) { 331 Assert.fail("Spawn of subprocess failed", ioe); 332 } catch (InterruptedException inte) { 333 Assert.fail("InterruptedException", inte); 334 } finally { 335 processes.forEach((p, parent) -> { 336 if (p.isAlive()) { 337 ProcessUtil.printProcess(p); 338 p.destroyForcibly(); 339 } 340 }); 341 } 342 } 343 344 /** 345 * Test (Not really a test) that dumps the list of all Processes. 346 */ 347 @Test 348 public static void test4() { 349 printf(" Parent Child Info%n"); 350 Stream<ProcessHandle> s = ProcessHandle.allProcesses(); 351 ProcessHandle[] processes = s.toArray(ProcessHandle[]::new); 352 int len = processes.length; 353 ProcessHandle[] parent = new ProcessHandle[len]; 354 Set<ProcessHandle> processesSet = 355 Arrays.stream(processes).collect(Collectors.toSet()); 356 Integer[] sortindex = new Integer[len]; 357 for (int i = 0; i < len; i++) { 358 sortindex[i] = i; 359 } 360 for (int i = 0; i < len; i++) { 361 parent[sortindex[i]] = processes[sortindex[i]].parent().orElse(null); 362 } 363 Arrays.sort(sortindex, (i1, i2) -> { 364 int cmp = Long.compare((parent[i1] == null ? 0L : parent[i1].pid()), 365 (parent[i2] == null ? 0L : parent[i2].pid())); 366 if (cmp == 0) { 367 cmp = Long.compare((processes[i1] == null ? 0L : processes[i1].pid()), 368 (processes[i2] == null ? 0L : processes[i2].pid())); 369 } 370 return cmp; 371 }); 372 boolean fail = false; 373 for (int i = 0; i < len; i++) { 374 ProcessHandle p = processes[sortindex[i]]; 375 ProcessHandle p_parent = parent[sortindex[i]]; 376 ProcessHandle.Info info = p.info(); 377 String indent = " "; 378 if (p_parent != null) { 379 if (!processesSet.contains(p_parent)) { 380 fail = true; 381 indent = "*** "; 382 } 383 } 384 printf("%s %7s, %7s, %s%n", indent, p_parent, p, info); 385 } 386 Assert.assertFalse(fail, "Parents missing from all Processes"); 387 388 } 389 390 /** 391 * A test for scale; launch a large number (14) of subprocesses. 392 */ 393 @Test 394 public static void test5() { 395 ConcurrentHashMap<ProcessHandle, ProcessHandle> processes = new ConcurrentHashMap<>(); 396 397 int factor = 2; 398 JavaChild p1 = null; 399 Instant start = Instant.now(); 400 try { 401 p1 = JavaChild.spawnJavaChild("stdin"); 402 ProcessHandle p1Handle = p1.toHandle(); 403 404 printf("Spawning %d x %d x %d processes, pid: %d%n", 405 factor, factor, factor, p1.pid()); 406 407 // Start the first tier of subprocesses 408 p1.sendAction("spawn", factor, "stdin"); 409 410 // Start the second tier of subprocesses 411 p1.sendAction("child", "spawn", factor, "stdin"); 412 413 // Start the third tier of subprocesses 414 p1.sendAction("child", "child", "spawn", factor, "stdin"); 415 416 int newChildren = factor * (1 + factor * (1 + factor)); 417 CountDownLatch spawnCount = new CountDownLatch(newChildren); 418 419 // Gather the PIDs from the output of the spawning process 420 p1.forEachOutputLine((s) -> { 421 String[] split = s.trim().split(" "); 422 if (split.length == 3 && split[1].equals("spawn")) { 423 Long child = Long.valueOf(split[2]); 424 Long parent = Long.valueOf(split[0].split(":")[0]); 425 processes.put(ProcessHandle.of(child).get(), ProcessHandle.of(parent).get()); 426 spawnCount.countDown(); 427 } 428 }); 429 430 // Wait for all the subprocesses to be listed as started 431 Assert.assertTrue(spawnCount.await(Utils.adjustTimeout(30L), TimeUnit.SECONDS), 432 "Timeout waiting for processes to start"); 433 434 // Debugging; list descendants that are not expected in processes 435 List<ProcessHandle> descendants = ProcessUtil.getDescendants(p1Handle); 436 long count = descendants.stream() 437 .filter(ph -> !processes.containsKey(ph)) 438 .count(); 439 if (count > 0) { 440 descendants.stream() 441 .filter(ph -> !processes.containsKey(ph)) 442 .forEach(ph1 -> ProcessUtil.printProcess(ph1, "Extra process: ")); 443 ProcessUtil.logTaskList(); 444 Assert.assertEquals(0, count, "Extra processes in descendants"); 445 } 446 447 Assert.assertEquals(getChildren(p1Handle).size(), 448 factor, "expected direct children"); 449 count = getDescendants(p1Handle).size(); 450 long totalChildren = factor * factor * factor + factor * factor + factor; 451 Assert.assertTrue(count >= totalChildren, 452 "expected at least " + totalChildren + ", actual: " + count); 453 454 List<ProcessHandle> subprocesses = getDescendants(p1Handle); 455 printf(" descendants: %s%n", 456 subprocesses.stream().map(p -> p.pid()) 457 .collect(Collectors.toList())); 458 459 p1.getOutputStream().close(); // Close stdin for the controlling p1 460 p1.waitFor(); 461 } catch (InterruptedException | IOException ex) { 462 Assert.fail("Unexpected Exception", ex); 463 } finally { 464 printf("Duration: %s%n", Duration.between(start, Instant.now())); 465 if (p1 != null) { 466 p1.destroyForcibly(); 467 } 468 processes.forEach((p, parent) -> { 469 if (p.isAlive()) { 470 ProcessUtil.printProcess(p, "Process Cleanup: "); 471 p.destroyForcibly(); 472 } 473 }); 474 } 475 } 476 477 }