/* * Copyright (c) 2015, 2022, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions.
*/
/** * This is a framework to launch an app that could be synchronized with caller * to make further attach actions reliable across supported platforms
* Caller example: * * LingeredApp a = LingeredApp.startApp(cmd); * // do something. * // a.getPid(). a.getProcess(), a.getProcessStdout() are available. * LingeredApp.stopApp(a); * * for use custom LingeredApp (class SmartTestApp extends LingeredApp): * * SmartTestApp a = new SmartTestApp(); * LingeredApp.startApp(a, cmd); * // do something * a.stopApp(); // LingeredApp.stopApp(a) can be used as well * * or fine grained control * * a = new SmartTestApp("MyLock.lck"); * a.createLock(); * a.runAppExactJvmOpts(Utils.getTestJavaOpts()); * a.waitAppReadyOrCrashed(); * // do something * a.deleteLock(); * a.waitAppTerminate(); * * After app termination (stopApp/waitAppTermination) its output is available * * output = a.getAppOutput(); *
*/ publicclass LingeredApp {
protectedboolean forceCrash = false; // set true to force a crash and core file
/** * Create LingeredApp object on caller side. Lock file have be a valid filename * at writable location * * @param lockFileName - the name of lock file
*/ public LingeredApp(String lockFileName) { this.lockFileName = lockFileName;
}
public LingeredApp() { final String lockName = UUID.randomUUID().toString() + ".lck"; this.lockFileName = lockName;
}
/** * * @return pid of java process running testapp
*/ publiclong getPid() { if (appProcess == null) { thrownew RuntimeException("Process is not alive");
} return appProcess.pid();
}
/** * * @return process object
*/ public Process getProcess() { return appProcess;
}
/** * @return the LingeredApp's output. * Can be called after the app is run.
*/ public String getProcessStdout() { return stdoutBuffer.toString();
}
/** * * @return OutputBuffer object for the LingeredApp's output. Can only be called * after LingeredApp has exited.
*/ public OutputBuffer getOutput() { if (appProcess.isAlive()) { thrownew RuntimeException("Process is still alive. Can't get its output.");
} if (output == null) {
output = OutputBuffer.of(stdoutBuffer.toString(), stderrBuffer.toString(), appProcess.exitValue());
} return output;
}
/* * Capture all stdout and stderr output from the LingeredApp so it can be returned * to the driver app later. This code is modeled after ProcessTools.getOutput().
*/ privatevoid startOutputPumpers() {
stderrBuffer = new ByteArrayOutputStream();
stdoutBuffer = new ByteArrayOutputStream();
StreamPumper outPumper = new StreamPumper(appProcess.getInputStream(), stdoutBuffer);
StreamPumper errPumper = new StreamPumper(appProcess.getErrorStream(), stderrBuffer);
outPumperThread = newThread(outPumper);
errPumperThread = newThread(errPumper);
/* Make sure all part of the app use the same method to get dates, as different methods could produce different results
*/ privatestaticlong epoch() { returnnew Date().getTime();
}
publicvoid waitAppTerminate() { // This code is modeled after tail end of ProcessTools.getOutput(). try { // If the app hangs, we don't want to wait for the to test timeout. if (!appProcess.waitFor(Utils.adjustTimeout(appWaitTime), TimeUnit.SECONDS)) {
appProcess.destroy();
appProcess.waitFor();
}
outPumperThread.join();
errPumperThread.join();
} catch (InterruptedException e) { Thread.currentThread().interrupt(); // pass
}
}
/** * The app touches the lock file when it's started * wait while it happens. Caller have to delete lock on wait error. * * @param timeout timeout in seconds * @throws java.io.IOException
*/ publicvoid waitAppReadyOrCrashed(long timeout) throws IOException { // adjust timeout for timeout_factor and convert to ms
timeout = Utils.adjustTimeout(timeout) * 1000; long here = epoch(); while (true) { // Check for crash or lock modification now, and immediately after sleeping for spinDelay each loop. if (!appProcess.isAlive()) { if (forceCrash) { return; // This is expected. Just return.
} else { thrownew IOException("App exited unexpectedly with " + appProcess.exitValue());
}
}
// Live process should touch lock file every second long lm = lastModified(lockFileName); if (lm > lockCreationTime) { break;
}
long timeTaken = epoch() - here; if (timeTaken > timeout) { thrownew IOException("Timeout: app not started or crashed in " + timeTaken + "ms");
} try { Thread.sleep(spinDelay);
} catch (InterruptedException ex) { // pass
}
}
}
/** * Waits for the application to start with the default timeout.
*/ publicvoid waitAppReadyOrCrashed() throws IOException {
waitAppReadyOrCrashed(forceCrash ? appCoreWaitTime : appWaitTime);
}
/** * Analyze an environment and prepare a command line to * run the app, app name should be added explicitly
*/ private List<String> runAppPrepare(String[] vmArguments) {
List<String> cmd = new ArrayList<>();
cmd.add(JDKToolFinder.getTestJDKTool("java"));
Collections.addAll(cmd, vmArguments); if (forceCrash) {
cmd.add("-XX:+CreateCoredumpOnCrash"); // We need to find libLingeredApp.so for the crash() native method
cmd.add("-Djava.library.path=" + System.getProperty("java.library.path"));
}
if (useDefaultClasspath()) { // Make sure we set correct classpath to run the app
cmd.add("-cp");
String classpath = System.getProperty("test.class.path");
cmd.add((classpath == null) ? "." : classpath);
}
return cmd;
}
/** * Adds application name to the command line. * By default adds name of this class.
*/ protectedvoid runAddAppName(List<String> cmd) {
cmd.add(getClass().getName());
}
/** * Assemble command line to a printable string
*/ publicvoid printCommandLine(List<String> cmd) { // A bit of verbosity
System.out.println(cmd.stream()
.map(s -> "'" + s + "'")
.collect(Collectors.joining(" ", "Command line: [", "]")));
}
/** * Run the app. * User should provide exact options to run app. Might use #Utils.getTestJavaOpts() to set default test options. * @param vmOpts * @throws IOException
*/ publicvoid runAppExactJvmOpts(String[] vmOpts) throws IOException {
List<String> cmd = runAppPrepare(vmOpts);
runAddAppName(cmd);
cmd.add(lockFileName); if (forceCrash) {
cmd.add("forceCrash"); // Let the subprocess know to force a crash
}
printCommandLine(cmd);
ProcessBuilder pb = new ProcessBuilder(cmd); if (forceCrash) { // If we are going to force a core dump, apply "ulimit -c unlimited" if we can.
pb = CoreUtils.addCoreUlimitCommand(pb);
} // ProcessBuilder.start can throw IOException
appProcess = pb.start();
/** * Delete lock file that signals app to terminate, then * wait until app is actually terminated. * @throws IOException
*/ publicvoid stopApp() throws IOException {
deleteLock(); // The startApp() of the derived app can throw // an exception before the LA actually starts if (appProcess != null) {
waitAppTerminate();
finishApp();
int exitcode = appProcess.exitValue(); if (exitcode != 0) { thrownew IOException("LingeredApp terminated with non-zero exit code " + exitcode);
}
}
}
/** * High level interface for test writers
*/
/** * Factory method that starts pre-created LingeredApp * lock name is autogenerated * User should provide exact options to run app. Might use #Utils.getTestJavaOpts() to set default test options. * @param jvmOpts - the exact vm options used to start LingeredApp * @param theApp - app to start * @throws IOException
*/ publicstaticvoid startAppExactJvmOpts(LingeredApp theApp, String... jvmOpts) throws IOException { long t1 = System.currentTimeMillis();
theApp.createLock(); try {
theApp.runAppExactJvmOpts(jvmOpts);
theApp.waitAppReadyOrCrashed();
} catch (Exception ex) { boolean alive = theApp.getProcess() != null && theApp.getProcess().isAlive();
System.out.println("LingeredApp failed to start or failed to crash. isAlive=" + alive + ": " + ex); // stopApp in case it is still alive, may be able to get output: if (alive) {
theApp.stopApp();
}
alive = theApp.getProcess() != null && theApp.getProcess().isAlive(); if (!alive) {
theApp.finishApp(); // Calls getOutput(), fails if still alive
}
theApp.deleteLock(); throw ex;
} finally { long t2 = System.currentTimeMillis();
System.out.println("LingeredApp startup took " + (t2 - t1) + "ms");
checkForDumps();
}
}
/** * Show any dump files of interest in the current directory.
*/ publicstaticvoid checkForDumps() {
System.out.println("Check for hs_err_pid/core/mdmp files:"); int count = 0;
FilenameFilter filter = (dir, file) -> (file.startsWith("hs_err_pid") || file.startsWith("core") || file.endsWith("mdmp")); for (File f : new File(".").listFiles(filter)) { long fileSize = f.length();
System.out.println(f + " " + (fileSize / 1024 / 1024) + "mb (" + fileSize + " bytes)");
count++;
} if (count == 0) {
System.out.println("None.");
}
}
/** * Factory method that starts pre-created LingeredApp * lock name is autogenerated, additionalJvmOpts are appended to default test options * @param additionalJvmOpts - additional Jvm options, appended to #Utils.getTestJavaOpts(); * @param theApp - app to start * @throws IOException
*/ publicstaticvoid startApp(LingeredApp theApp, String... additionalJvmOpts) throws IOException {
startAppExactJvmOpts(theApp, Utils.prependTestJavaOpts(additionalJvmOpts));
}
/** * Factory method that creates LingeredApp object with ready to use application * lock name is autogenerated, additionalJvmOpts are appended to default test options * @param additionalJvmOpts - additional Jvm options, appended to #Utils.getTestJavaOpts(); * @return LingeredApp object * @throws IOException
*/ publicstatic LingeredApp startApp(String... additionalJvmOpts) throws IOException {
LingeredApp a = new LingeredApp();
startApp(a, additionalJvmOpts); return a;
}
publicstaticvoid stopApp(LingeredApp app) throws IOException { if (app != null) { // LingeredApp can throw an exception during the intialization, // make sure we don't have cascade NPE
app.stopApp();
}
}
/** * LastModified time might not work correctly in some cases it might * cause later failures
*/
publicstaticboolean isLastModifiedWorking() { boolean sane = true; try { long lm = lastModified("."); if (lm == 0) {
System.err.println("SANITY Warning! The lastModifiedTime() doesn't work on this system, it returns 0");
sane = false;
}
long now = epoch(); if (lm > now) {
System.err.println("SANITY Warning! The Clock is wrong on this system lastModifiedTime() > getTime()");
sane = false;
}
setLastModified(".", epoch()); long lm1 = lastModified("."); if (lm1 <= lm) {
System.err.println("SANITY Warning! The setLastModified doesn't work on this system");
sane = false;
}
} catch(IOException e) {
System.err.println("SANITY Warning! IOException during sanity check " + e);
sane = false;
}
return sane;
}
/** * Support for creating a thread whose stack trace is always readable. There are * occassional isues trying to get the stack trace for LingeredApp.main() since * it sometimes wakes up from sleep and may breifly have an unreadable thread * stack trace. The code below is used to create "SteadyStateThread" whose * stack trace is always readable.
*/
// Wait until the thread has started running. while (!steadyStateReached) { Thread.onSpinWait();
}
// Now wait until we get into the synchronized block. while (steadyStateThread.getState() != Thread.State.BLOCKED) { Thread.onSpinWait();
}
}
/** * This part is the application itself. First arg is optional "forceCrash". * Following arg is the lock file name.
*/ publicstaticvoid main(String args[]) { boolean forceCrash = false;
if (args.length == 0) {
System.err.println("Lock file name is not specified");
System.exit(7);
} elseif (args.length > 2) {
System.err.println("Too many arguments specified: " + args.length);
System.exit(7);
}
try {
Object steadyStateObj = new Object(); synchronized(steadyStateObj) {
startSteadyStateThread(steadyStateObj); if (forceCrash) {
System.loadLibrary("LingeredApp"); // location of native crash() method
crash();
} while (Files.exists(path)) { // Touch the lock to indicate our readiness
setLastModified(theLockFileName, epoch()); Thread.sleep(spinDelay);
}
}
} catch (IOException ex) { // Lock deleted while we are setting last modified time. // Ignore the error and let the app exit. if (Files.exists(path)) { // If the lock file was not removed, return an error.
System.err.println("LingeredApp IOException: lock file still exists");
System.exit(4);
}
} catch (Exception ex) {
System.err.println("LingeredApp ERROR: " + ex); // Leave exit_code = 1 to Java launcher
System.exit(3);
}
System.exit(0);
}
}
Messung V0.5
¤ Dauer der Verarbeitung: 0.3 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.