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