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