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