Path: blob/master/test/jdk/java/lang/ProcessHandle/JavaChild.java
41149 views
/*1* Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import com.sun.management.OperatingSystemMXBean;24import java.io.File;25import java.io.InputStream;26import java.io.OutputStream;27import java.io.InputStreamReader;28import java.io.BufferedReader;29import java.io.IOException;30import java.io.Reader;31import java.io.PrintWriter;32import java.lang.InterruptedException;33import java.lang.Override;34import java.lang.management.ManagementFactory;35import java.time.Instant;36import java.util.ArrayList;37import java.util.Arrays;38import java.util.Collections;39import java.util.concurrent.CompletableFuture;40import java.util.concurrent.ExecutionException;41import java.util.HashSet;42import java.util.List;43import java.util.Set;44import java.util.Optional;45import java.util.function.Consumer;464748/**49* Command driven subprocess with useful child functions.50*/51public class JavaChild extends Process {5253private static volatile int commandSeq = 0; // Command sequence number54private static final ProcessHandle self = ProcessHandle.current();55private static int finalStatus = 0;56private static final List<JavaChild> children = new ArrayList<>();57private static final Set<JavaChild> completedChildren =58Collections.synchronizedSet(new HashSet<>());5960private final Process delegate;61private final PrintWriter inputWriter;62private final BufferedReader outputReader;636465/**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 child69* @param delegate the process to delegate and send commands to and get responses from70*/71private JavaChild(ProcessBuilder pb) throws IOException {72allArgs = pb.command();73delegate = pb.start();74// Initialize PrintWriter with autoflush (on println)75inputWriter = new PrintWriter(delegate.getOutputStream(), true);76outputReader = new BufferedReader(new InputStreamReader(delegate.getInputStream()));77}7879@Override80public void destroy() {81delegate.destroy();82}8384@Override85public int exitValue() {86return delegate.exitValue();87}8889@Override90public int waitFor() throws InterruptedException {91return delegate.waitFor();92}9394@Override95public OutputStream getOutputStream() {96return delegate.getOutputStream();97}9899@Override100public InputStream getInputStream() {101return delegate.getInputStream();102}103104@Override105public InputStream getErrorStream() {106return delegate.getErrorStream();107}108109@Override110public ProcessHandle toHandle() {111return delegate.toHandle();112}113114@Override115public CompletableFuture<Process> onExit() {116return delegate.onExit();117}118@Override119public String toString() {120return "delegate: " + delegate.toString();121}122123public List<String> getArgs() {124return allArgs;125}126127public CompletableFuture<JavaChild> onJavaChildExit() {128return onExit().thenApply(ph -> this);129}130131/**132* Send an action and arguments to the child via stdin.133* @param action the action134* @param args additional arguments135* @throws IOException if something goes wrong writing to the child136*/137void sendAction(String action, Object... args) throws IOException {138StringBuilder sb = new StringBuilder();139sb.append(action);140for (Object arg :args) {141sb.append(" ");142sb.append(arg);143}144String cmd = sb.toString();145synchronized (this) {146inputWriter.println(cmd);147}148}149150public BufferedReader outputReader() {151return outputReader;152}153154/**155* Asynchronously evaluate each line of output received back from the child process.156* @param consumer a Consumer of each line read from the child157* @return a CompletableFuture that is completed when the child closes System.out.158*/159CompletableFuture<String> forEachOutputLine(Consumer<String> consumer) {160final CompletableFuture<String> future = new CompletableFuture<>();161String name = "OutputLineReader-" + pid();162Thread t = new Thread(() -> {163try (BufferedReader reader = outputReader()) {164String line;165while ((line = reader.readLine()) != null) {166consumer.accept(line);167}168} catch (IOException | RuntimeException ex) {169consumer.accept("IOE (" + pid() + "):" + ex.getMessage());170future.completeExceptionally(ex);171}172future.complete("success");173}, name);174t.start();175return future;176}177178/**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 logging183* output for all subprocesses.184* @param args the command line arguments to JavaChild185* @return the JavaChild that was started186* @throws IOException thrown by ProcessBuilder.start187*/188static JavaChild spawnJavaChild(Object... args) throws IOException {189String[] stringArgs = new String[args.length];190for (int i = 0; i < args.length; i++) {191stringArgs[i] = args[i].toString();192}193ProcessBuilder pb = build(stringArgs);194pb.redirectError(ProcessBuilder.Redirect.INHERIT);195return new JavaChild(pb);196}197198/**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 JavaChild202* @return the Process that was started203* @throws IOException thrown by ProcessBuilder.start204*/205static Process spawn(String... args) throws IOException {206ProcessBuilder pb = build(args);207pb.inheritIO();208return pb.start();209}210211/**212* Return a ProcessBuilder with the javaChildArgs and213* any additional supplied args.214*215* @param args the command line arguments to JavaChild216* @return the ProcessBuilder217*/218static ProcessBuilder build(String ... args) {219ProcessBuilder pb = new ProcessBuilder();220List<String> list = new ArrayList<>(javaChildArgs);221for (String arg : args)222list.add(arg);223pb.command(list);224return pb;225}226227static final String javaHome = (System.getProperty("test.jdk") != null)228? System.getProperty("test.jdk")229: System.getProperty("java.home");230231static final String javaExe =232javaHome + File.separator + "bin" + File.separator + "java";233234static final String classpath =235System.getProperty("java.class.path");236237static final List<String> javaChildArgs =238Arrays.asList(javaExe,239"-XX:+DisplayVMOutputToStderr",240"-Dtest.jdk=" + javaHome,241"-classpath", absolutifyPath(classpath),242"JavaChild");243244// Will hold the complete list of arguments which was given to Processbuilder.command()245private List<String> allArgs;246247private static String absolutifyPath(String path) {248StringBuilder sb = new StringBuilder();249for (String file : path.split(File.pathSeparator)) {250if (sb.length() != 0)251sb.append(File.pathSeparator);252sb.append(new File(file).getAbsolutePath());253}254return sb.toString();255}256257/**258* Main program that interprets commands from the command line args or stdin.259* Each command produces output to stdout confirming the command and260* 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 needed264*/265public static void main(String[] args) {266System.out.printf("args: %s %s%n", ProcessHandle.current(), Arrays.toString(args));267interpretCommands(args);268System.exit(finalStatus);269}270271/**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 needed275*/276private static void interpretCommands(String[] args) {277try {278int nextArg = 0;279while (nextArg < args.length) {280String action = args[nextArg++];281switch (action) {282case "help":283sendResult(action, "");284help();285break;286case "sleep":287int millis = Integer.valueOf(args[nextArg++]);288Thread.sleep(millis);289sendResult(action, Integer.toString(millis));290break;291case "cpuloop":292long cpuMillis = Long.valueOf(args[nextArg++]);293long cpuTarget = getCpuTime() + cpuMillis * 1_000_000L;294while (getCpuTime() < cpuTarget) {295// burn the cpu until the time is up296}297sendResult(action, cpuMillis);298break;299case "cputime":300sendResult(action, getCpuTime());301break;302case "out":303case "err":304String value = args[nextArg++];305sendResult(action, value);306if (action.equals("err")) {307System.err.println(value);308}309break;310case "stdin":311// Read commands from stdin; at eof, close stdin of312// children and wait for each to exit313sendResult(action, "start");314try (Reader reader = new InputStreamReader(System.in);315BufferedReader input = new BufferedReader(reader)) {316String line;317while ((line = input.readLine()) != null) {318line = line.trim();319if (!line.isEmpty()) {320String[] split = line.split("\\s");321interpretCommands(split);322}323}324// EOF on stdin, close stdin on all spawned processes325for (JavaChild p : children) {326try {327p.getOutputStream().close();328} catch (IOException ie) {329sendResult("stdin_closing", p.pid(),330"exception", ie.getMessage());331}332}333334for (JavaChild p : children) {335do {336try {337p.waitFor();338break;339} catch (InterruptedException e) {340// retry341}342} while (true);343}344// Wait for all children to be gone345Instant timeOut = Instant.now().plusSeconds(10L);346while (!completedChildren.containsAll(children)) {347if (Instant.now().isBefore(timeOut)) {348Thread.sleep(100L);349} else {350System.err.printf("Timeout waiting for " +351"children to terminate%n");352children.removeAll(completedChildren);353for (JavaChild c : children) {354sendResult("stdin_noterm", c.pid());355System.err.printf(" Process not terminated: " +356"pid: %d%n", c.pid());357}358System.exit(2);359}360}361}362sendResult(action, "done");363return; // normal exit from JavaChild Process364case "parent":365sendResult(action, self.parent().toString());366break;367case "pid":368sendResult(action, self.toString());369break;370case "exit":371int exitValue = (nextArg < args.length)372? Integer.valueOf(args[nextArg]) : 0;373sendResult(action, exitValue);374System.exit(exitValue);375break;376case "spawn": {377if (args.length - nextArg < 2) {378throw new RuntimeException("not enough args for respawn: " +379(args.length - 2));380}381// Spawn as many children as requested and382// pass on rest of the arguments383int ncount = Integer.valueOf(args[nextArg++]);384Object[] subargs = new String[args.length - nextArg];385System.arraycopy(args, nextArg, subargs, 0, subargs.length);386for (int i = 0; i < ncount; i++) {387JavaChild p = spawnJavaChild(subargs);388sendResult(action, p.pid());389p.forEachOutputLine(JavaChild::sendRaw);390p.onJavaChildExit().thenAccept((p1) -> {391int excode = p1.exitValue();392sendResult("child_exit", p1.pid(), excode);393completedChildren.add(p1);394});395children.add(p); // Add child to spawned list396}397nextArg = args.length;398break;399}400case "child": {401// Send the command to all the live children;402// ignoring those that are not alive403int sentCount = 0;404Object[] result =405Arrays.copyOfRange(args, nextArg - 1, args.length);406Object[] subargs =407Arrays.copyOfRange(args, nextArg + 1, args.length);408for (JavaChild p : children) {409if (p.isAlive()) {410sentCount++;411// overwrite with current pid412result[0] = Long.toString(p.pid());413sendResult(action, result);414p.sendAction(args[nextArg], subargs);415}416}417if (sentCount == 0) {418sendResult(action, "n/a");419}420nextArg = args.length;421break;422}423case "child_eof" :424// Close the InputStream of all the live children;425// ignoring those that are not alive426for (JavaChild p : children) {427if (p.isAlive()) {428sendResult(action, p.pid());429p.getOutputStream().close();430}431}432break;433case "property":434String name = args[nextArg++];435sendResult(action, name, System.getProperty(name));436break;437case "threaddump":438Thread.dumpStack();439break;440case "waitpid":441long pid = Long.parseLong(args[nextArg++]);442Optional<String> s = ProcessHandle.of(pid).map(ph -> waitAlive(ph));443sendResult(action, s.orElse("pid not valid: " + pid));444break;445default:446throw new Error("JavaChild action unknown: " + action);447}448}449} catch (Throwable t) {450t.printStackTrace(System.err);451System.exit(1);452}453}454455private static String waitAlive(ProcessHandle ph) {456String status;457try {458boolean isAlive = ph.onExit().get().isAlive();459status = Boolean.toString(isAlive);460} catch (InterruptedException | ExecutionException ex ) {461status = "interrupted";462}463return status;464}465466static synchronized void sendRaw(String s) {467System.out.println(s);468System.out.flush();469}470static void sendResult(String action, Object... results) {471sendRaw(new Event(action, results).toString());472}473474static long getCpuTime() {475OperatingSystemMXBean osMbean =476(OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();477return osMbean.getProcessCpuTime();478}479480/**481* Print command usage to stderr.482*/483private static void help() {484System.err.println("Commands:");485System.err.println(" help");486System.err.println(" pid");487System.err.println(" parent");488System.err.println(" cpuloop <loopcount>");489System.err.println(" cputime");490System.err.println(" stdin - read commands from stdin");491System.err.println(" sleep <millis>");492System.err.println(" spawn <n> command... - spawn n new children and send command");493System.err.println(" child command... - send command to all live children");494System.err.println(" child_eof - send eof to all live children");495System.err.println(" waitpid <pid> - wait for the pid to exit");496System.err.println(" exit <exitcode>");497System.err.println(" out arg...");498System.err.println(" err arg...");499}500501static class Event {502long pid;503long seq;504String command;505Object[] results;506Event(String command, Object... results) {507this(self.pid(), ++commandSeq, command, results);508}509Event(long pid, int seq, String command, Object... results) {510this.pid = pid;511this.seq = seq;512this.command = command;513this.results = results;514}515516/**517* Create a String encoding the pid, seq, command, and results.518*519* @return a String formatted to send to the stream.520*/521String format() {522StringBuilder sb = new StringBuilder();523sb.append(pid);524sb.append(":");525sb.append(seq);526sb.append(" ");527sb.append(command);528for (int i = 0; i < results.length; i++) {529sb.append(" ");530sb.append(results[i]);531}532return sb.toString();533}534535Event(String encoded) {536String[] split = encoded.split("\\s");537String[] pidSeq = split[0].split(":");538pid = Long.valueOf(pidSeq[0]);539seq = Integer.valueOf(pidSeq[1]);540command = split[1];541Arrays.copyOfRange(split, 1, split.length);542}543544public String toString() {545return format();546}547548}549}550551552