1 /* 2 * Copyright (c) 2015, 2019, 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 package jdk.test.failurehandler.action; 25 26 import com.sun.tools.attach.VirtualMachine; 27 import com.sun.tools.attach.VirtualMachineDescriptor; 28 import jdk.test.failurehandler.value.InvalidValueException; 29 import jdk.test.failurehandler.value.Value; 30 import jdk.test.failurehandler.value.ValueHandler; 31 import jdk.test.failurehandler.HtmlSection; 32 import jdk.test.failurehandler.Stopwatch; 33 import jdk.test.failurehandler.Utils; 34 35 import java.io.BufferedReader; 36 import java.io.CharArrayReader; 37 import java.io.CharArrayWriter; 38 import java.io.IOException; 39 import java.io.InputStreamReader; 40 import java.io.PrintWriter; 41 import java.io.Reader; 42 import java.io.Writer; 43 import java.io.File; 44 import java.nio.file.Path; 45 import java.nio.file.Paths; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.Collections; 49 import java.util.Date; 50 import java.util.List; 51 import java.util.Properties; 52 import java.util.Timer; 53 import java.util.TimerTask; 54 import java.util.concurrent.TimeUnit; 55 56 public 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(null, 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 timeout=%s%n%1$s%n", line, new Date(), pb.command(), params.timeout); 168 169 Process process; 170 KillerTask killer; 171 172 ExitCode result = ExitCode.NEVER_STARTED; 173 174 try { 175 process = pb.start(); 176 killer = new KillerTask(process); 177 killer.schedule(params.timeout); 178 Utils.copyStream(new InputStreamReader(process.getInputStream()), 179 out); 180 try { 181 result = new ExitCode(process.waitFor()); 182 } catch (InterruptedException e) { 183 log.println("WARNING: interrupted when waiting for the tool:%n"); 184 e.printStackTrace(log); 185 } finally { 186 killer.cancel(); 187 } 188 if (killer.hasTimedOut()) { 189 log.printf( 190 "WARNING: tool timed out: killed process after %d ms%n", 191 params.timeout); 192 result = ExitCode.TIMED_OUT; 193 } 194 } catch (IOException e) { 195 log.printf("WARNING: caught IOException while running tool%n"); 196 e.printStackTrace(log); 197 result = ExitCode.LAUNCH_ERROR; 198 } 199 200 stopwatch.stop(); 201 log.printf("%s%n[%tF %<tT] exit code: %d time: %d ms%n%1$s%n", 202 line, new Date(), result.value, 203 TimeUnit.NANOSECONDS.toMillis(stopwatch.getElapsedTimeNs())); 204 return result; 205 } 206 207 public void runPatternAction(SimpleAction action, HtmlSection section) { 208 if (action != null) { 209 HtmlSection subSection = action.getSection(section); 210 PrintWriter log = subSection.getWriter(); 211 ProcessBuilder pb = action.prepareProcess(log, this); 212 exec(subSection, pb, action.getParameters()); 213 } 214 } 215 216 public void runPatternAction(PatternAction action, HtmlSection section, 217 String value) { 218 if (action != null) { 219 ProcessBuilder pb = action.prepareProcess(section, this, value); 220 HtmlSection subSection = action.getSection(section); 221 exec(subSection, pb, action.getParameters()); 222 } 223 } 224 225 public boolean isJava(long pid, PrintWriter log) { 226 ProcessBuilder pb = prepareProcess(log, "jps", "-q"); 227 if (pb == null) { 228 return false; 229 } 230 pb.redirectErrorStream(true); 231 boolean result = false; 232 String pidStr = "" + pid; 233 try { 234 Process process = pb.start(); 235 try (BufferedReader reader = new BufferedReader( 236 new InputStreamReader(process.getInputStream()))) { 237 String line; 238 while ((line = reader.readLine()) != null){ 239 if (pidStr.equals(line)) { 240 result = true; 241 } 242 } 243 } 244 process.waitFor(); 245 } catch (IOException e) { 246 log.printf("WARNING: can't run jps : %s%n", e.getMessage()); 247 e.printStackTrace(log); 248 } catch (InterruptedException e) { 249 log.printf("WARNING: interrupted%n"); 250 e.printStackTrace(log); 251 } 252 return result; 253 } 254 255 private static class KillerTask extends TimerTask { 256 private static final Timer WATCHDOG = new Timer("WATCHDOG", true); 257 private final Process process; 258 private boolean timedOut; 259 260 public KillerTask(Process process) { 261 this.process = process; 262 } 263 264 public void run() { 265 try { 266 process.exitValue(); 267 } catch (IllegalThreadStateException e) { 268 process.destroyForcibly(); 269 timedOut = true; 270 } 271 } 272 273 public boolean hasTimedOut() { 274 return timedOut; 275 } 276 277 public void schedule(long timeout) { 278 if (timeout > 0) { 279 WATCHDOG.schedule(this, timeout); 280 } 281 } 282 } 283 284 private void exec(HtmlSection section, ProcessBuilder process, 285 ActionParameters params) { 286 if (process == null) { 287 return; 288 } 289 PrintWriter sectionWriter = section.getWriter(); 290 if (params.repeat > 1) { 291 for (int i = 0, n = params.repeat; i < n; ++i) { 292 HtmlSection iteration = section.createChildren( 293 String.format("iteration_%d", i)); 294 PrintWriter writer = iteration.getWriter(); 295 ExitCode exitCode = run(writer, writer, process, params); 296 if (params.stopOnError && !ExitCode.OK.equals(exitCode)) { 297 sectionWriter.printf( 298 "ERROR: non zero exit code[%d] -- break.", 299 exitCode.value); 300 break; 301 } 302 // sleep, if this is not the last iteration 303 if (i < n - 1) { 304 try { 305 Thread.sleep(params.pause); 306 } catch (InterruptedException e) { 307 sectionWriter.printf( 308 "WARNING: interrupted while sleeping between invocations"); 309 e.printStackTrace(sectionWriter); 310 } 311 } 312 } 313 } else { 314 run(section.getWriter(), section.getWriter(), process, params); 315 } 316 } 317 318 /** 319 * Special values for prepareProcess exit code. 320 * 321 * <p>Can we clash with normal codes? 322 * On Linux, only [0..255] are returned. 323 * On Windows, prepareProcess exit codes are stored in unsigned int. 324 * On MacOSX no limits (except it should fit C int type) 325 * are defined in the exit() man pages. 326 */ 327 private static class ExitCode { 328 /** Process exits gracefully */ 329 public static final ExitCode OK = new ExitCode(0); 330 /** Error launching prepareProcess */ 331 public static final ExitCode LAUNCH_ERROR = new ExitCode(-1); 332 /** Application prepareProcess has been killed by watchdog due to timeout */ 333 public static final ExitCode TIMED_OUT = new ExitCode(-2); 334 /** Application prepareProcess has never been started due to program logic */ 335 public static final ExitCode NEVER_STARTED = new ExitCode(-3); 336 337 public final int value; 338 339 private ExitCode(int value) { 340 this.value = value; 341 } 342 343 @Override 344 public boolean equals(Object o) { 345 if (this == o) { 346 return true; 347 } 348 if (o == null || getClass() != o.getClass()) { 349 return false; 350 } 351 352 ExitCode exitCode = (ExitCode) o; 353 return value == exitCode.value; 354 } 355 356 @Override 357 public int hashCode() { 358 return value; 359 } 360 } 361 362 }