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 }