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