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