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