1 /*
  2  * Copyright (c) 2014, 2023, 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             List<ProcessHandle> subprocesses = getChildren(p1Handle);
448             printf(" children:  %s%n",
449                     subprocesses.stream().map(p -> p.pid())
450                     .collect(Collectors.toList()));
451 
452             Assert.assertEquals(getChildren(p1Handle).size(),
453                     factor, "expected direct children");
454             count = getDescendants(p1Handle).size();
455             long totalChildren = factor * factor * factor + factor * factor + factor;
456             Assert.assertTrue(count >= totalChildren,
457                     "expected at least " + totalChildren + ", actual: " + count);
458 
459             List<ProcessHandle> descSubprocesses = getDescendants(p1Handle);
460             printf(" descendants:  %s%n",
461                     descSubprocesses.stream().map(p -> p.pid())
462                     .collect(Collectors.toList()));
463 
464             p1.getOutputStream().close();  // Close stdin for the controlling p1
465             p1.waitFor();
466         } catch (InterruptedException | IOException ex) {
467             Assert.fail("Unexpected Exception", ex);
468         } finally {
469             printf("Duration: %s%n", Duration.between(start, Instant.now()));
470             if (p1 != null) {
471                 p1.destroyForcibly();
472             }
473             processes.forEach((p, parent) -> {
474                 if (p.isAlive()) {
475                     ProcessUtil.printProcess(p, "Process Cleanup: ");
476                     p.destroyForcibly();
477                 }
478             });
479         }
480     }
481 
482 }