ActionHelper.java revision 1870:4aa2e64eff30
1/* 2 * Copyright (c) 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 24package jdk.test.failurehandler.action; 25 26import com.sun.tools.attach.VirtualMachine; 27import com.sun.tools.attach.VirtualMachineDescriptor; 28import jdk.test.failurehandler.value.InvalidValueException; 29import jdk.test.failurehandler.value.Value; 30import jdk.test.failurehandler.value.ValueHandler; 31import jdk.test.failurehandler.HtmlSection; 32import jdk.test.failurehandler.Stopwatch; 33import jdk.test.failurehandler.Utils; 34 35import java.io.BufferedReader; 36import java.io.CharArrayReader; 37import java.io.CharArrayWriter; 38import java.io.IOException; 39import java.io.InputStreamReader; 40import java.io.PrintWriter; 41import java.io.Reader; 42import java.io.Writer; 43import java.io.File; 44import java.nio.file.Path; 45import java.nio.file.Paths; 46import java.util.ArrayList; 47import java.util.Arrays; 48import java.util.Collections; 49import java.util.Date; 50import java.util.List; 51import java.util.Properties; 52import java.util.Timer; 53import java.util.TimerTask; 54import java.util.concurrent.TimeUnit; 55 56public class ActionHelper { 57 private final Path workDir; 58 @Value(name = "execSuffix") 59 private String executableSuffix = ""; 60 private Path[] paths; 61 62 private final PatternAction getChildren; 63 64 public ActionHelper(Path workDir, String prefix, Properties properties, 65 Path... jdks) throws InvalidValueException { 66 this.workDir = workDir.toAbsolutePath(); 67 getChildren = new PatternAction("children", 68 Utils.prependPrefix(prefix, "getChildren"), properties); 69 ValueHandler.apply(this, properties, prefix); 70 String[] pathStrings = System.getenv("PATH").split(File.pathSeparator); 71 paths = new Path[pathStrings.length]; 72 for (int i = 0; i < paths.length; ++i) { 73 paths[i] = Paths.get(pathStrings[i]); 74 } 75 addJdks(jdks); 76 } 77 78 public List<Long> getChildren(HtmlSection section, long pid) { 79 String pidStr = "" + pid; 80 ProcessBuilder pb = getChildren.prepareProcess(section, this, pidStr); 81 PrintWriter log = getChildren.getSection(section).getWriter(); 82 CharArrayWriter writer = new CharArrayWriter(); 83 ExitCode code = run(log, writer, pb, getChildren.getParameters()); 84 Reader output = new CharArrayReader(writer.toCharArray()); 85 86 if (!ExitCode.OK.equals(code)) { 87 log.println("WARNING: get children pids action failed"); 88 try { 89 Utils.copyStream(output, log); 90 } catch (IOException e) { 91 e.printStackTrace(log); 92 } 93 return Collections.emptyList(); 94 } 95 96 List<Long> result = new ArrayList<>(); 97 try { 98 try (BufferedReader reader = new BufferedReader(output)) { 99 String line; 100 while ((line = reader.readLine()) != null) { 101 String value = line.trim(); 102 if (value.isEmpty()) { 103 // ignore empty lines 104 continue; 105 } 106 try { 107 result.add(Long.valueOf(value)); 108 } catch (NumberFormatException e) { 109 log.printf("WARNING: can't parse child pid %s : %s%n", 110 line, e.getMessage()); 111 e.printStackTrace(log); 112 } 113 } 114 } 115 } catch (IOException e) { 116 e.printStackTrace(log); 117 } 118 return result; 119 } 120 121 public ProcessBuilder prepareProcess(PrintWriter log, String app, 122 String... args) { 123 File appBin = findApp(app); 124 if (appBin == null) { 125 log.printf("ERROR: can't find %s in %s.%n", 126 app, Arrays.toString(paths)); 127 return null; 128 } 129 List<String> command = new ArrayList<>(args.length + 1); 130 command.add(appBin.toString()); 131 Collections.addAll(command, args); 132 return new ProcessBuilder() 133 .command(command) 134 .directory(workDir.toFile()); 135 } 136 137 private File findApp(String app) { 138 String name = app + executableSuffix; 139 for (Path pathElem : paths) { 140 File result = pathElem.resolve(name).toFile(); 141 if (result.exists()) { 142 return result; 143 } 144 } 145 return null; 146 } 147 148 private void addJdks(Path[] jdkPaths) { 149 if (jdkPaths != null && jdkPaths.length != 0) { 150 Path[] result = new Path[jdkPaths.length + paths.length]; 151 for (int i = 0; i < jdkPaths.length; ++i) { 152 result[i] = jdkPaths[i].resolve("bin"); 153 } 154 System.arraycopy(paths, 0, result, jdkPaths.length, paths.length); 155 paths = result; 156 } 157 } 158 159 private ExitCode run(PrintWriter log, Writer out, ProcessBuilder pb, 160 ActionParameters params) { 161 char[] lineChars = new char[40]; 162 Arrays.fill(lineChars, '-'); 163 String line = new String(lineChars); 164 Stopwatch stopwatch = new Stopwatch(); 165 stopwatch.start(); 166 167 log.printf("%s%n[%tF %<tT] %s%n%1$s%n", line, new Date(), pb.command()); 168 Process process; 169 KillerTask killer; 170 171 ExitCode result = ExitCode.NEVER_STARTED; 172 173 try { 174 process = pb.start(); 175 killer = new KillerTask(process); 176 killer.schedule(params.timeout); 177 Utils.copyStream(new InputStreamReader(process.getInputStream()), 178 out); 179 try { 180 result = new ExitCode(process.waitFor()); 181 killer.cancel(); 182 } catch (InterruptedException e) { 183 Thread.currentThread().interrupt(); 184 if (!killer.cancel()) { 185 log.println( 186 "WARNING: interrupted when waiting for the tool:"); 187 e.printStackTrace(log); 188 } 189 } 190 if (killer.hasTimedOut()) { 191 log.printf( 192 "WARNING: tool timed out: killed process after %d ms%n", 193 TimeUnit.MILLISECONDS.toMicros(params.timeout)); 194 result = ExitCode.TIMED_OUT; 195 } 196 } catch (IOException e) { 197 e.printStackTrace(log); 198 result = ExitCode.LAUNCH_ERROR; 199 } 200 201 stopwatch.stop(); 202 log.printf("%s%n[%tF %<tT] exit code : %d time : %d ms%n%1$s%n", 203 line, new Date(), result.value, 204 TimeUnit.MILLISECONDS.toSeconds(stopwatch.getElapsedTimeNs())); 205 return result; 206 } 207 208 public void runPatternAction(SimpleAction action, HtmlSection section) { 209 if (action != null) { 210 HtmlSection subSection = action.getSection(section); 211 PrintWriter log = subSection.getWriter(); 212 ProcessBuilder pb = action.prepareProcess(log, this); 213 exec(subSection, pb, action.getParameters()); 214 } 215 } 216 217 public void runPatternAction(PatternAction action, HtmlSection section, 218 String value) { 219 if (action != null) { 220 ProcessBuilder pb = action.prepareProcess(section, this, value); 221 HtmlSection subSection = action.getSection(section); 222 exec(subSection, pb, action.getParameters()); 223 } 224 } 225 226 public boolean isJava(long pid, PrintWriter log) { 227 ProcessBuilder pb = prepareProcess(log, "jps", "-q"); 228 if (pb == null) { 229 return false; 230 } 231 pb.redirectErrorStream(true); 232 boolean result = false; 233 String pidStr = "" + pid; 234 try { 235 Process process = pb.start(); 236 try (BufferedReader reader = new BufferedReader( 237 new InputStreamReader(process.getInputStream()))) { 238 String line; 239 while ((line = reader.readLine()) != null){ 240 if (pidStr.equals(line)) { 241 result = true; 242 } 243 } 244 } 245 process.waitFor(); 246 } catch (IOException e) { 247 log.printf("WARNING: can't run jps : %s%n", e.getMessage()); 248 e.printStackTrace(log); 249 } catch (InterruptedException e) { 250 Thread.currentThread().interrupt(); 251 e.printStackTrace(log); 252 } 253 return result; 254 } 255 256 private static class KillerTask extends TimerTask { 257 private static final Timer WATCHDOG = new Timer("WATCHDOG", true); 258 private final Process process; 259 private boolean timedOut; 260 261 public KillerTask(Process process) { 262 this.process = process; 263 } 264 265 public void run() { 266 try { 267 process.exitValue(); 268 } catch (IllegalThreadStateException e) { 269 // !prepareProcess.isAlive() 270 process.destroy(); 271 timedOut = true; 272 } 273 } 274 275 public boolean hasTimedOut() { 276 return timedOut; 277 } 278 279 public void schedule(long timeout) { 280 if (timeout > 0) { 281 WATCHDOG.schedule(this, timeout); 282 } 283 } 284 } 285 286 private void exec(HtmlSection section, ProcessBuilder process, 287 ActionParameters params) { 288 if (process == null) { 289 return; 290 } 291 PrintWriter sectionWriter = section.getWriter(); 292 if (params.repeat > 1) { 293 for (int i = 0, n = params.repeat; i < n; ++i) { 294 HtmlSection iteration = section.createChildren( 295 String.format("iteration_%d", i)); 296 PrintWriter writer = iteration.getWriter(); 297 ExitCode exitCode = run(writer, writer, process, params); 298 if (params.stopOnError && !ExitCode.OK.equals(exitCode)) { 299 sectionWriter.printf( 300 "ERROR: non zero exit code[%d] -- break.", 301 exitCode.value); 302 break; 303 } 304 try { 305 Thread.sleep(params.pause); 306 } catch (InterruptedException e) { 307 Thread.currentThread().interrupt(); 308 e.printStackTrace(sectionWriter); 309 } 310 } 311 } else { 312 run(section.getWriter(), section.getWriter(), process, params); 313 } 314 } 315 316 /** 317 * Special values for prepareProcess exit code. 318 * 319 * <p>Can we clash with normal codes? 320 * On Solaris and Linux, only [0..255] are returned. 321 * On Windows, prepareProcess exit codes are stored in unsigned int. 322 * On MacOSX no limits (except it should fit C int type) 323 * are defined in the exit() man pages. 324 */ 325 private static class ExitCode { 326 /** Process exits gracefully */ 327 public static final ExitCode OK = new ExitCode(0); 328 /** Error launching prepareProcess */ 329 public static final ExitCode LAUNCH_ERROR = new ExitCode(-1); 330 /** Application prepareProcess has been killed by watchdog due to timeout */ 331 public static final ExitCode TIMED_OUT = new ExitCode(-2); 332 /** Application prepareProcess has never been started due to program logic */ 333 public static final ExitCode NEVER_STARTED = new ExitCode(-3); 334 335 public final int value; 336 337 private ExitCode(int value) { 338 this.value = value; 339 } 340 341 @Override 342 public boolean equals(Object o) { 343 if (this == o) { 344 return true; 345 } 346 if (o == null || getClass() != o.getClass()) { 347 return false; 348 } 349 350 ExitCode exitCode = (ExitCode) o; 351 return value == exitCode.value; 352 } 353 354 @Override 355 public int hashCode() { 356 return value; 357 } 358 } 359 360} 361