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 }