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 24import com.sun.management.OperatingSystemMXBean; 25import java.io.File; 26import java.io.InputStream; 27import java.io.OutputStream; 28import java.io.InputStreamReader; 29import java.io.BufferedReader; 30import java.io.IOException; 31import java.io.Reader; 32import java.io.PrintWriter; 33import java.lang.InterruptedException; 34import java.lang.Override; 35import java.lang.management.ManagementFactory; 36import java.time.Instant; 37import java.util.ArrayList; 38import java.util.Arrays; 39import java.util.Collections; 40import java.util.concurrent.CompletableFuture; 41import java.util.concurrent.ExecutionException; 42import java.util.HashSet; 43import java.util.List; 44import java.util.Set; 45import java.util.Optional; 46import java.util.function.Consumer; 47 48 49/** 50 * Command driven subprocess with useful child functions. 51 */ 52public class JavaChild extends Process { 53 54private 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} 551