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