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
24import com.sun.management.OperatingSystemMXBean;
25import java.io.File;
26import java.io.InputStream;
27import java.io.OutputStream;
28import java.io.InputStreamReader;
29import java.io.BufferedReader;
30import java.io.IOException;
31import java.io.Reader;
32import java.io.PrintWriter;
33import java.lang.InterruptedException;
34import java.lang.Override;
35import java.lang.management.ManagementFactory;
36import java.time.Instant;
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.Collections;
40import java.util.concurrent.CompletableFuture;
41import java.util.concurrent.ExecutionException;
42import java.util.HashSet;
43import java.util.List;
44import java.util.Set;
45import java.util.Optional;
46import java.util.function.Consumer;
47
48
49/**
50 * Command driven subprocess with useful child functions.
51 */
52public class JavaChild extends Process {
53
54private static volatile int commandSeq = 0;         // Command sequence number
55    private static final ProcessHandle self = ProcessHandle.current();
56    private static int finalStatus = 0;
57    private static final List<JavaChild> children = new ArrayList<>();
58    private static final Set<JavaChild> completedChildren =
59            Collections.synchronizedSet(new HashSet<>());
60
61    private final Process delegate;
62    private final PrintWriter inputWriter;
63    private final BufferedReader outputReader;
64
65
66    /**
67     * Create a JavaChild control instance that delegates to the spawned process.
68     * {@link #sendAction} is used to send commands via the processes stdin.
69     * {@link #forEachOutputLine} can be used to process output from the child
70     * @param delegate the process to delegate and send commands to and get responses from
71     */
72    private JavaChild(ProcessBuilder pb) throws IOException {
73        allArgs = pb.command();
74        delegate = pb.start();
75        // Initialize PrintWriter with autoflush (on println)
76        inputWriter = new PrintWriter(delegate.getOutputStream(), true);
77        outputReader = new BufferedReader(new InputStreamReader(delegate.getInputStream()));
78    }
79
80    @Override
81    public void destroy() {
82        delegate.destroy();
83    }
84
85    @Override
86    public int exitValue() {
87        return delegate.exitValue();
88    }
89
90    @Override
91    public int waitFor() throws InterruptedException {
92        return delegate.waitFor();
93    }
94
95    @Override
96    public OutputStream getOutputStream() {
97        return delegate.getOutputStream();
98    }
99
100    @Override
101    public InputStream getInputStream() {
102        return delegate.getInputStream();
103    }
104
105    @Override
106    public InputStream getErrorStream() {
107        return delegate.getErrorStream();
108    }
109
110    @Override
111    public ProcessHandle toHandle() {
112        return delegate.toHandle();
113    }
114
115    @Override
116    public CompletableFuture<Process> onExit() {
117        return delegate.onExit();
118    }
119    @Override
120    public String toString() {
121        return "delegate: " + delegate.toString();
122    }
123
124    public List<String> getArgs() {
125        return allArgs;
126    }
127
128    public CompletableFuture<JavaChild> onJavaChildExit() {
129        return onExit().thenApply(ph -> this);
130    }
131
132    /**
133     * Send an action and arguments to the child via stdin.
134     * @param action the action
135     * @param args additional arguments
136     * @throws IOException if something goes wrong writing to the child
137     */
138    void sendAction(String action, Object... args) throws IOException {
139        StringBuilder sb = new StringBuilder();
140        sb.append(action);
141        for (Object arg :args) {
142            sb.append(" ");
143            sb.append(arg);
144        }
145        String cmd = sb.toString();
146        synchronized (this) {
147            inputWriter.println(cmd);
148        }
149    }
150
151    public BufferedReader outputReader() {
152        return outputReader;
153    }
154
155    /**
156     * Asynchronously evaluate each line of output received back from the child process.
157     * @param consumer a Consumer of each line read from the child
158     * @return a CompletableFuture that is completed when the child closes System.out.
159     */
160    CompletableFuture<String> forEachOutputLine(Consumer<String> consumer) {
161        final CompletableFuture<String> future = new CompletableFuture<>();
162        String name = "OutputLineReader-" + pid();
163        Thread t = new Thread(() -> {
164            try (BufferedReader reader = outputReader()) {
165                String line;
166                while ((line = reader.readLine()) != null) {
167                    consumer.accept(line);
168                }
169            } catch (IOException | RuntimeException ex) {
170                consumer.accept("IOE (" + pid() + "):" + ex.getMessage());
171                future.completeExceptionally(ex);
172            }
173            future.complete("success");
174        }, name);
175        t.start();
176        return future;
177    }
178
179    /**
180     * Spawn a JavaChild with the provided arguments.
181     * Commands can be send to the child with {@link #sendAction}.
182     * Output lines from the child can be processed with {@link #forEachOutputLine}.
183     * System.err is set to inherit and is the unstructured async logging
184     * output for all subprocesses.
185     * @param args the command line arguments to JavaChild
186     * @return the JavaChild that was started
187     * @throws IOException thrown by ProcessBuilder.start
188     */
189    static JavaChild spawnJavaChild(Object... args) throws IOException {
190        String[] stringArgs = new String[args.length];
191        for (int i = 0; i < args.length; i++) {
192            stringArgs[i] = args[i].toString();
193        }
194        ProcessBuilder pb = build(stringArgs);
195        pb.redirectError(ProcessBuilder.Redirect.INHERIT);
196        return new JavaChild(pb);
197    }
198
199    /**
200     * Spawn a JavaChild with the provided arguments.
201     * Sets the process to inherit the I/O channels.
202     * @param args the command line arguments to JavaChild
203     * @return the Process that was started
204     * @throws IOException thrown by ProcessBuilder.start
205     */
206    static Process spawn(String... args) throws IOException {
207        ProcessBuilder pb = build(args);
208        pb.inheritIO();
209        return pb.start();
210    }
211
212    /**
213     * Return a ProcessBuilder with the javaChildArgs and
214     * any additional supplied args.
215     *
216     * @param args the command line arguments to JavaChild
217     * @return the ProcessBuilder
218     */
219    static ProcessBuilder build(String ... args) {
220        ProcessBuilder pb = new ProcessBuilder();
221        List<String> list = new ArrayList<>(javaChildArgs);
222        for (String arg : args)
223            list.add(arg);
224        pb.command(list);
225        return pb;
226    }
227
228    static final String javaHome = (System.getProperty("test.jdk") != null)
229            ? System.getProperty("test.jdk")
230            : System.getProperty("java.home");
231
232    static final String javaExe =
233            javaHome + File.separator + "bin" + File.separator + "java";
234
235    static final String classpath =
236            System.getProperty("java.class.path");
237
238    static final List<String> javaChildArgs =
239            Arrays.asList(javaExe,
240                    "-XX:+DisplayVMOutputToStderr",
241                    "-Dtest.jdk=" + javaHome,
242                    "-classpath", absolutifyPath(classpath),
243                    "JavaChild");
244
245    // Will hold the complete list of arguments which was given to Processbuilder.command()
246    private List<String> allArgs;
247
248    private static String absolutifyPath(String path) {
249        StringBuilder sb = new StringBuilder();
250        for (String file : path.split(File.pathSeparator)) {
251            if (sb.length() != 0)
252                sb.append(File.pathSeparator);
253            sb.append(new File(file).getAbsolutePath());
254        }
255        return sb.toString();
256    }
257
258    /**
259     * Main program that interprets commands from the command line args or stdin.
260     * Each command produces output to stdout confirming the command and
261     * providing results.
262     * System.err is used for unstructured information.
263     * @param args an array of strings to be interpreted as commands;
264     *             each command uses additional arguments as needed
265     */
266    public static void main(String[] args) {
267        System.out.printf("args: %s %s%n", ProcessHandle.current(), Arrays.toString(args));
268        interpretCommands(args);
269        System.exit(finalStatus);
270    }
271
272    /**
273     * Interpret an array of strings as a command line.
274     * @param args an array of strings to be interpreted as commands;
275     *             each command uses additional arguments as needed
276     */
277    private static void interpretCommands(String[] args) {
278        try {
279            int nextArg = 0;
280            while (nextArg < args.length) {
281                String action = args[nextArg++];
282                switch (action) {
283                    case "help":
284                        sendResult(action, "");
285                        help();
286                        break;
287                    case "sleep":
288                        int millis = Integer.valueOf(args[nextArg++]);
289                        Thread.sleep(millis);
290                        sendResult(action, Integer.toString(millis));
291                        break;
292                    case "cpuloop":
293                        long cpuMillis = Long.valueOf(args[nextArg++]);
294                        long cpuTarget = getCpuTime() + cpuMillis * 1_000_000L;
295                        while (getCpuTime() < cpuTarget) {
296                            // burn the cpu until the time is up
297                        }
298                        sendResult(action, cpuMillis);
299                        break;
300                    case "cputime":
301                        sendResult(action, getCpuTime());
302                        break;
303                    case "out":
304                    case "err":
305                        String value = args[nextArg++];
306                        sendResult(action, value);
307                        if (action.equals("err")) {
308                            System.err.println(value);
309                        }
310                        break;
311                    case "stdin":
312                        // Read commands from stdin;  at eof, close stdin of
313                        // children and wait for each to exit
314                        sendResult(action, "start");
315                        try (Reader reader = new InputStreamReader(System.in);
316                             BufferedReader input = new BufferedReader(reader)) {
317                            String line;
318                            while ((line = input.readLine()) != null) {
319                                line = line.trim();
320                                if (!line.isEmpty()) {
321                                    String[] split = line.split("\\s");
322                                    interpretCommands(split);
323                                }
324                            }
325                            // EOF on stdin, close stdin on all spawned processes
326                            for (JavaChild p : children) {
327                                try {
328                                    p.getOutputStream().close();
329                                } catch (IOException ie) {
330                                    sendResult("stdin_closing", p.pid(),
331                                            "exception", ie.getMessage());
332                                }
333                            }
334
335                            for (JavaChild p : children) {
336                                do {
337                                    try {
338                                        p.waitFor();
339                                        break;
340                                    } catch (InterruptedException e) {
341                                        // retry
342                                    }
343                                } while (true);
344                            }
345                            // Wait for all children to be gone
346                            Instant timeOut = Instant.now().plusSeconds(10L);
347                            while (!completedChildren.containsAll(children)) {
348                                if (Instant.now().isBefore(timeOut)) {
349                                    Thread.sleep(100L);
350                                } else {
351                                    System.err.printf("Timeout waiting for " +
352                                            "children to terminate%n");
353                                    children.removeAll(completedChildren);
354                                    for (JavaChild c : children) {
355                                        sendResult("stdin_noterm", c.pid());
356                                        System.err.printf("  Process not terminated: " +
357                                                "pid: %d%n", c.pid());
358                                    }
359                                    System.exit(2);
360                                }
361                            }
362                        }
363                        sendResult(action, "done");
364                        return;                 // normal exit from JavaChild Process
365                    case "parent":
366                        sendResult(action, self.parent().toString());
367                        break;
368                    case "pid":
369                        sendResult(action, self.toString());
370                        break;
371                    case "exit":
372                        int exitValue = (nextArg < args.length)
373                                ?  Integer.valueOf(args[nextArg]) : 0;
374                        sendResult(action, exitValue);
375                        System.exit(exitValue);
376                        break;
377                    case "spawn": {
378                        if (args.length - nextArg < 2) {
379                            throw new RuntimeException("not enough args for respawn: " +
380                                    (args.length - 2));
381                        }
382                        // Spawn as many children as requested and
383                        // pass on rest of the arguments
384                        int ncount = Integer.valueOf(args[nextArg++]);
385                        Object[] subargs = new String[args.length - nextArg];
386                        System.arraycopy(args, nextArg, subargs, 0, subargs.length);
387                        for (int i = 0; i < ncount; i++) {
388                            JavaChild p = spawnJavaChild(subargs);
389                            sendResult(action, p.pid());
390                            p.forEachOutputLine(JavaChild::sendRaw);
391                            p.onJavaChildExit().thenAccept((p1) -> {
392                                int excode = p1.exitValue();
393                                sendResult("child_exit", p1.pid(), excode);
394                                completedChildren.add(p1);
395                            });
396                            children.add(p);        // Add child to spawned list
397                        }
398                        nextArg = args.length;
399                        break;
400                    }
401                    case "child": {
402                        // Send the command to all the live children;
403                        // ignoring those that are not alive
404                        int sentCount = 0;
405                        Object[] result =
406                                Arrays.copyOfRange(args, nextArg - 1, args.length);
407                        Object[] subargs =
408                                Arrays.copyOfRange(args, nextArg + 1, args.length);
409                        for (JavaChild p : children) {
410                            if (p.isAlive()) {
411                                sentCount++;
412                                // overwrite with current pid
413                                result[0] = Long.toString(p.pid());
414                                sendResult(action, result);
415                                p.sendAction(args[nextArg], subargs);
416                            }
417                        }
418                        if (sentCount == 0) {
419                            sendResult(action, "n/a");
420                        }
421                        nextArg = args.length;
422                        break;
423                    }
424                    case "child_eof" :
425                        // Close the InputStream of all the live children;
426                        // ignoring those that are not alive
427                        for (JavaChild p : children) {
428                            if (p.isAlive()) {
429                                sendResult(action, p.pid());
430                                p.getOutputStream().close();
431                            }
432                        }
433                        break;
434                    case "property":
435                        String name = args[nextArg++];
436                        sendResult(action, name, System.getProperty(name));
437                        break;
438                    case "threaddump":
439                        Thread.dumpStack();
440                        break;
441                    case "waitpid":
442                        long pid = Long.parseLong(args[nextArg++]);
443                        Optional<String> s = ProcessHandle.of(pid).map(ph -> waitAlive(ph));
444                        sendResult(action, s.orElse("pid not valid: " + pid));
445                        break;
446                    default:
447                        throw new Error("JavaChild action unknown: " + action);
448                }
449            }
450        } catch (Throwable t) {
451            t.printStackTrace(System.err);
452            System.exit(1);
453        }
454    }
455
456    private static String waitAlive(ProcessHandle ph) {
457        String status;
458        try {
459            boolean isAlive = ph.onExit().get().isAlive();
460            status = Boolean.toString(isAlive);
461        } catch (InterruptedException | ExecutionException ex ) {
462            status = "interrupted";
463        }
464        return status;
465    }
466
467    static synchronized void sendRaw(String s) {
468        System.out.println(s);
469        System.out.flush();
470    }
471    static void sendResult(String action, Object... results) {
472        sendRaw(new Event(action, results).toString());
473    }
474
475    static long getCpuTime() {
476        OperatingSystemMXBean osMbean =
477                (OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();
478        return osMbean.getProcessCpuTime();
479    }
480
481    /**
482     * Print command usage to stderr.
483     */
484    private static void help() {
485        System.err.println("Commands:");
486        System.err.println("  help");
487        System.err.println("  pid");
488        System.err.println("  parent");
489        System.err.println("  cpuloop <loopcount>");
490        System.err.println("  cputime");
491        System.err.println("  stdin - read commands from stdin");
492        System.err.println("  sleep <millis>");
493        System.err.println("  spawn <n> command... - spawn n new children and send command");
494        System.err.println("  child command... - send command to all live children");
495        System.err.println("  child_eof - send eof to all live children");
496        System.err.println("  waitpid <pid> - wait for the pid to exit");
497        System.err.println("  exit <exitcode>");
498        System.err.println("  out arg...");
499        System.err.println("  err arg...");
500    }
501
502    static class Event {
503        long pid;
504        long seq;
505        String command;
506        Object[] results;
507        Event(String command, Object... results) {
508            this(self.pid(), ++commandSeq, command, results);
509        }
510        Event(long pid, int seq, String command, Object... results) {
511            this.pid = pid;
512            this.seq = seq;
513            this.command = command;
514            this.results = results;
515        }
516
517        /**
518         * Create a String encoding the pid, seq, command, and results.
519         *
520         * @return a String formatted  to send to the stream.
521         */
522        String format() {
523            StringBuilder sb = new StringBuilder();
524            sb.append(pid);
525            sb.append(":");
526            sb.append(seq);
527            sb.append(" ");
528            sb.append(command);
529            for (int i = 0; i < results.length; i++) {
530                sb.append(" ");
531                sb.append(results[i]);
532            }
533            return sb.toString();
534        }
535
536        Event(String encoded) {
537            String[] split = encoded.split("\\s");
538            String[] pidSeq = split[0].split(":");
539            pid = Long.valueOf(pidSeq[0]);
540            seq = Integer.valueOf(pidSeq[1]);
541            command = split[1];
542            Arrays.copyOfRange(split, 1, split.length);
543        }
544
545        public String toString() {
546            return format();
547        }
548
549    }
550}
551