test/jdk/java/lang/ProcessHandle/JavaChild.java
changeset 47216 71c04702a3d5
parent 44640 590dec7cadb4
equal deleted inserted replaced
47215:4ebc2e2fb97c 47216:71c04702a3d5
       
     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 
       
    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.Reader;
       
    32 import java.io.PrintWriter;
       
    33 import java.lang.InterruptedException;
       
    34 import java.lang.Override;
       
    35 import java.lang.management.ManagementFactory;
       
    36 import java.time.Instant;
       
    37 import java.util.ArrayList;
       
    38 import java.util.Arrays;
       
    39 import java.util.Collections;
       
    40 import java.util.concurrent.CompletableFuture;
       
    41 import java.util.concurrent.ExecutionException;
       
    42 import java.util.HashSet;
       
    43 import java.util.List;
       
    44 import java.util.Set;
       
    45 import java.util.Optional;
       
    46 import java.util.function.Consumer;
       
    47 
       
    48 
       
    49 /**
       
    50  * Command driven subprocess with useful child functions.
       
    51  */
       
    52 public class JavaChild extends Process {
       
    53 
       
    54 private 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 }