--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/ProcessHandle/JavaChild.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,550 @@
+/*
+ * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import com.sun.management.OperatingSystemMXBean;
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.InputStreamReader;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.PrintWriter;
+import java.lang.InterruptedException;
+import java.lang.Override;
+import java.lang.management.ManagementFactory;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Optional;
+import java.util.function.Consumer;
+
+
+/**
+ * Command driven subprocess with useful child functions.
+ */
+public class JavaChild extends Process {
+
+private static volatile int commandSeq = 0; // Command sequence number
+ private static final ProcessHandle self = ProcessHandle.current();
+ private static int finalStatus = 0;
+ private static final List<JavaChild> children = new ArrayList<>();
+ private static final Set<JavaChild> completedChildren =
+ Collections.synchronizedSet(new HashSet<>());
+
+ private final Process delegate;
+ private final PrintWriter inputWriter;
+ private final BufferedReader outputReader;
+
+
+ /**
+ * Create a JavaChild control instance that delegates to the spawned process.
+ * {@link #sendAction} is used to send commands via the processes stdin.
+ * {@link #forEachOutputLine} can be used to process output from the child
+ * @param delegate the process to delegate and send commands to and get responses from
+ */
+ private JavaChild(ProcessBuilder pb) throws IOException {
+ allArgs = pb.command();
+ delegate = pb.start();
+ // Initialize PrintWriter with autoflush (on println)
+ inputWriter = new PrintWriter(delegate.getOutputStream(), true);
+ outputReader = new BufferedReader(new InputStreamReader(delegate.getInputStream()));
+ }
+
+ @Override
+ public void destroy() {
+ delegate.destroy();
+ }
+
+ @Override
+ public int exitValue() {
+ return delegate.exitValue();
+ }
+
+ @Override
+ public int waitFor() throws InterruptedException {
+ return delegate.waitFor();
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return delegate.getOutputStream();
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return delegate.getInputStream();
+ }
+
+ @Override
+ public InputStream getErrorStream() {
+ return delegate.getErrorStream();
+ }
+
+ @Override
+ public ProcessHandle toHandle() {
+ return delegate.toHandle();
+ }
+
+ @Override
+ public CompletableFuture<Process> onExit() {
+ return delegate.onExit();
+ }
+ @Override
+ public String toString() {
+ return "delegate: " + delegate.toString();
+ }
+
+ public List<String> getArgs() {
+ return allArgs;
+ }
+
+ public CompletableFuture<JavaChild> onJavaChildExit() {
+ return onExit().thenApply(ph -> this);
+ }
+
+ /**
+ * Send an action and arguments to the child via stdin.
+ * @param action the action
+ * @param args additional arguments
+ * @throws IOException if something goes wrong writing to the child
+ */
+ void sendAction(String action, Object... args) throws IOException {
+ StringBuilder sb = new StringBuilder();
+ sb.append(action);
+ for (Object arg :args) {
+ sb.append(" ");
+ sb.append(arg);
+ }
+ String cmd = sb.toString();
+ synchronized (this) {
+ inputWriter.println(cmd);
+ }
+ }
+
+ public BufferedReader outputReader() {
+ return outputReader;
+ }
+
+ /**
+ * Asynchronously evaluate each line of output received back from the child process.
+ * @param consumer a Consumer of each line read from the child
+ * @return a CompletableFuture that is completed when the child closes System.out.
+ */
+ CompletableFuture<String> forEachOutputLine(Consumer<String> consumer) {
+ final CompletableFuture<String> future = new CompletableFuture<>();
+ String name = "OutputLineReader-" + pid();
+ Thread t = new Thread(() -> {
+ try (BufferedReader reader = outputReader()) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ consumer.accept(line);
+ }
+ } catch (IOException | RuntimeException ex) {
+ consumer.accept("IOE (" + pid() + "):" + ex.getMessage());
+ future.completeExceptionally(ex);
+ }
+ future.complete("success");
+ }, name);
+ t.start();
+ return future;
+ }
+
+ /**
+ * Spawn a JavaChild with the provided arguments.
+ * Commands can be send to the child with {@link #sendAction}.
+ * Output lines from the child can be processed with {@link #forEachOutputLine}.
+ * System.err is set to inherit and is the unstructured async logging
+ * output for all subprocesses.
+ * @param args the command line arguments to JavaChild
+ * @return the JavaChild that was started
+ * @throws IOException thrown by ProcessBuilder.start
+ */
+ static JavaChild spawnJavaChild(Object... args) throws IOException {
+ String[] stringArgs = new String[args.length];
+ for (int i = 0; i < args.length; i++) {
+ stringArgs[i] = args[i].toString();
+ }
+ ProcessBuilder pb = build(stringArgs);
+ pb.redirectError(ProcessBuilder.Redirect.INHERIT);
+ return new JavaChild(pb);
+ }
+
+ /**
+ * Spawn a JavaChild with the provided arguments.
+ * Sets the process to inherit the I/O channels.
+ * @param args the command line arguments to JavaChild
+ * @return the Process that was started
+ * @throws IOException thrown by ProcessBuilder.start
+ */
+ static Process spawn(String... args) throws IOException {
+ ProcessBuilder pb = build(args);
+ pb.inheritIO();
+ return pb.start();
+ }
+
+ /**
+ * Return a ProcessBuilder with the javaChildArgs and
+ * any additional supplied args.
+ *
+ * @param args the command line arguments to JavaChild
+ * @return the ProcessBuilder
+ */
+ static ProcessBuilder build(String ... args) {
+ ProcessBuilder pb = new ProcessBuilder();
+ List<String> list = new ArrayList<>(javaChildArgs);
+ for (String arg : args)
+ list.add(arg);
+ pb.command(list);
+ return pb;
+ }
+
+ static final String javaHome = (System.getProperty("test.jdk") != null)
+ ? System.getProperty("test.jdk")
+ : System.getProperty("java.home");
+
+ static final String javaExe =
+ javaHome + File.separator + "bin" + File.separator + "java";
+
+ static final String classpath =
+ System.getProperty("java.class.path");
+
+ static final List<String> javaChildArgs =
+ Arrays.asList(javaExe,
+ "-XX:+DisplayVMOutputToStderr",
+ "-Dtest.jdk=" + javaHome,
+ "-classpath", absolutifyPath(classpath),
+ "JavaChild");
+
+ // Will hold the complete list of arguments which was given to Processbuilder.command()
+ private List<String> allArgs;
+
+ private static String absolutifyPath(String path) {
+ StringBuilder sb = new StringBuilder();
+ for (String file : path.split(File.pathSeparator)) {
+ if (sb.length() != 0)
+ sb.append(File.pathSeparator);
+ sb.append(new File(file).getAbsolutePath());
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Main program that interprets commands from the command line args or stdin.
+ * Each command produces output to stdout confirming the command and
+ * providing results.
+ * System.err is used for unstructured information.
+ * @param args an array of strings to be interpreted as commands;
+ * each command uses additional arguments as needed
+ */
+ public static void main(String[] args) {
+ System.out.printf("args: %s %s%n", ProcessHandle.current(), Arrays.toString(args));
+ interpretCommands(args);
+ System.exit(finalStatus);
+ }
+
+ /**
+ * Interpret an array of strings as a command line.
+ * @param args an array of strings to be interpreted as commands;
+ * each command uses additional arguments as needed
+ */
+ private static void interpretCommands(String[] args) {
+ try {
+ int nextArg = 0;
+ while (nextArg < args.length) {
+ String action = args[nextArg++];
+ switch (action) {
+ case "help":
+ sendResult(action, "");
+ help();
+ break;
+ case "sleep":
+ int millis = Integer.valueOf(args[nextArg++]);
+ Thread.sleep(millis);
+ sendResult(action, Integer.toString(millis));
+ break;
+ case "cpuloop":
+ long cpuMillis = Long.valueOf(args[nextArg++]);
+ long cpuTarget = getCpuTime() + cpuMillis * 1_000_000L;
+ while (getCpuTime() < cpuTarget) {
+ // burn the cpu until the time is up
+ }
+ sendResult(action, cpuMillis);
+ break;
+ case "cputime":
+ sendResult(action, getCpuTime());
+ break;
+ case "out":
+ case "err":
+ String value = args[nextArg++];
+ sendResult(action, value);
+ if (action.equals("err")) {
+ System.err.println(value);
+ }
+ break;
+ case "stdin":
+ // Read commands from stdin; at eof, close stdin of
+ // children and wait for each to exit
+ sendResult(action, "start");
+ try (Reader reader = new InputStreamReader(System.in);
+ BufferedReader input = new BufferedReader(reader)) {
+ String line;
+ while ((line = input.readLine()) != null) {
+ line = line.trim();
+ if (!line.isEmpty()) {
+ String[] split = line.split("\\s");
+ interpretCommands(split);
+ }
+ }
+ // EOF on stdin, close stdin on all spawned processes
+ for (JavaChild p : children) {
+ try {
+ p.getOutputStream().close();
+ } catch (IOException ie) {
+ sendResult("stdin_closing", p.pid(),
+ "exception", ie.getMessage());
+ }
+ }
+
+ for (JavaChild p : children) {
+ do {
+ try {
+ p.waitFor();
+ break;
+ } catch (InterruptedException e) {
+ // retry
+ }
+ } while (true);
+ }
+ // Wait for all children to be gone
+ Instant timeOut = Instant.now().plusSeconds(10L);
+ while (!completedChildren.containsAll(children)) {
+ if (Instant.now().isBefore(timeOut)) {
+ Thread.sleep(100L);
+ } else {
+ System.err.printf("Timeout waiting for " +
+ "children to terminate%n");
+ children.removeAll(completedChildren);
+ for (JavaChild c : children) {
+ sendResult("stdin_noterm", c.pid());
+ System.err.printf(" Process not terminated: " +
+ "pid: %d%n", c.pid());
+ }
+ System.exit(2);
+ }
+ }
+ }
+ sendResult(action, "done");
+ return; // normal exit from JavaChild Process
+ case "parent":
+ sendResult(action, self.parent().toString());
+ break;
+ case "pid":
+ sendResult(action, self.toString());
+ break;
+ case "exit":
+ int exitValue = (nextArg < args.length)
+ ? Integer.valueOf(args[nextArg]) : 0;
+ sendResult(action, exitValue);
+ System.exit(exitValue);
+ break;
+ case "spawn": {
+ if (args.length - nextArg < 2) {
+ throw new RuntimeException("not enough args for respawn: " +
+ (args.length - 2));
+ }
+ // Spawn as many children as requested and
+ // pass on rest of the arguments
+ int ncount = Integer.valueOf(args[nextArg++]);
+ Object[] subargs = new String[args.length - nextArg];
+ System.arraycopy(args, nextArg, subargs, 0, subargs.length);
+ for (int i = 0; i < ncount; i++) {
+ JavaChild p = spawnJavaChild(subargs);
+ sendResult(action, p.pid());
+ p.forEachOutputLine(JavaChild::sendRaw);
+ p.onJavaChildExit().thenAccept((p1) -> {
+ int excode = p1.exitValue();
+ sendResult("child_exit", p1.pid(), excode);
+ completedChildren.add(p1);
+ });
+ children.add(p); // Add child to spawned list
+ }
+ nextArg = args.length;
+ break;
+ }
+ case "child": {
+ // Send the command to all the live children;
+ // ignoring those that are not alive
+ int sentCount = 0;
+ Object[] result =
+ Arrays.copyOfRange(args, nextArg - 1, args.length);
+ Object[] subargs =
+ Arrays.copyOfRange(args, nextArg + 1, args.length);
+ for (JavaChild p : children) {
+ if (p.isAlive()) {
+ sentCount++;
+ // overwrite with current pid
+ result[0] = Long.toString(p.pid());
+ sendResult(action, result);
+ p.sendAction(args[nextArg], subargs);
+ }
+ }
+ if (sentCount == 0) {
+ sendResult(action, "n/a");
+ }
+ nextArg = args.length;
+ break;
+ }
+ case "child_eof" :
+ // Close the InputStream of all the live children;
+ // ignoring those that are not alive
+ for (JavaChild p : children) {
+ if (p.isAlive()) {
+ sendResult(action, p.pid());
+ p.getOutputStream().close();
+ }
+ }
+ break;
+ case "property":
+ String name = args[nextArg++];
+ sendResult(action, name, System.getProperty(name));
+ break;
+ case "threaddump":
+ Thread.dumpStack();
+ break;
+ case "waitpid":
+ long pid = Long.parseLong(args[nextArg++]);
+ Optional<String> s = ProcessHandle.of(pid).map(ph -> waitAlive(ph));
+ sendResult(action, s.orElse("pid not valid: " + pid));
+ break;
+ default:
+ throw new Error("JavaChild action unknown: " + action);
+ }
+ }
+ } catch (Throwable t) {
+ t.printStackTrace(System.err);
+ System.exit(1);
+ }
+ }
+
+ private static String waitAlive(ProcessHandle ph) {
+ String status;
+ try {
+ boolean isAlive = ph.onExit().get().isAlive();
+ status = Boolean.toString(isAlive);
+ } catch (InterruptedException | ExecutionException ex ) {
+ status = "interrupted";
+ }
+ return status;
+ }
+
+ static synchronized void sendRaw(String s) {
+ System.out.println(s);
+ System.out.flush();
+ }
+ static void sendResult(String action, Object... results) {
+ sendRaw(new Event(action, results).toString());
+ }
+
+ static long getCpuTime() {
+ OperatingSystemMXBean osMbean =
+ (OperatingSystemMXBean)ManagementFactory.getOperatingSystemMXBean();
+ return osMbean.getProcessCpuTime();
+ }
+
+ /**
+ * Print command usage to stderr.
+ */
+ private static void help() {
+ System.err.println("Commands:");
+ System.err.println(" help");
+ System.err.println(" pid");
+ System.err.println(" parent");
+ System.err.println(" cpuloop <loopcount>");
+ System.err.println(" cputime");
+ System.err.println(" stdin - read commands from stdin");
+ System.err.println(" sleep <millis>");
+ System.err.println(" spawn <n> command... - spawn n new children and send command");
+ System.err.println(" child command... - send command to all live children");
+ System.err.println(" child_eof - send eof to all live children");
+ System.err.println(" waitpid <pid> - wait for the pid to exit");
+ System.err.println(" exit <exitcode>");
+ System.err.println(" out arg...");
+ System.err.println(" err arg...");
+ }
+
+ static class Event {
+ long pid;
+ long seq;
+ String command;
+ Object[] results;
+ Event(String command, Object... results) {
+ this(self.pid(), ++commandSeq, command, results);
+ }
+ Event(long pid, int seq, String command, Object... results) {
+ this.pid = pid;
+ this.seq = seq;
+ this.command = command;
+ this.results = results;
+ }
+
+ /**
+ * Create a String encoding the pid, seq, command, and results.
+ *
+ * @return a String formatted to send to the stream.
+ */
+ String format() {
+ StringBuilder sb = new StringBuilder();
+ sb.append(pid);
+ sb.append(":");
+ sb.append(seq);
+ sb.append(" ");
+ sb.append(command);
+ for (int i = 0; i < results.length; i++) {
+ sb.append(" ");
+ sb.append(results[i]);
+ }
+ return sb.toString();
+ }
+
+ Event(String encoded) {
+ String[] split = encoded.split("\\s");
+ String[] pidSeq = split[0].split(":");
+ pid = Long.valueOf(pidSeq[0]);
+ seq = Integer.valueOf(pidSeq[1]);
+ command = split[1];
+ Arrays.copyOfRange(split, 1, split.length);
+ }
+
+ public String toString() {
+ return format();
+ }
+
+ }
+}