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 }