/*
* Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved.
*
* This file is part of BoofCV (http://boofcv.org).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package boofcv.gui;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Date;
import java.util.List;
/**
* Class for launching JVMs. Monitors the status and kills frozen threads. Keeps track of execution time and
* sets up class path.
*
* @author Peter Abeles
*/
public class JavaRuntimeLauncher {
String classPath;
// amount of memory allocated to the JVM
long memoryInMB = 200;
// if the process doesn't finish in this number of milliesconds it's considered frozen and killed
long frozenTime = 60*1000;
// amount of time it actually took to execute in milliseconds
long durationMilli;
volatile boolean killRequested = false;
// save for future debugging
String[] jvmArgs;
// Default to standard printOut and printErr
PrintStream printOut = System.out;
PrintStream printErr = System.err;
/**
* Constructor. Configures which library it is to be launching a class from/related to
* @param pathJars List of paths to all the jars
*/
public JavaRuntimeLauncher( List<String> pathJars ) {
String sep = System.getProperty("path.separator");
if( pathJars != null ) {
classPath = "";
for( String s : pathJars ) {
classPath = classPath + sep + s;
}
}
}
/**
* Specifies the amount of time the process has to complete. After which it is considered frozen and
* will be killed
* @param frozenTime time in milliseconds
*/
public void setFrozenTime(long frozenTime) {
this.frozenTime = frozenTime;
}
/**
* Specifies the amount of memory the process will be allocated in megabytes
* @param memoryInMB megabytes
*/
public void setMemoryInMB(long memoryInMB) {
this.memoryInMB = memoryInMB;
}
/**
* Returns how long the operation took to complete. In milliseconds
*/
public long getDurationMilli() {
return durationMilli;
}
/**
* Launches the class with the provided arguments. Blocks until the process stops.
*
* @param mainClass Class
* @param args it's arguments
* @return true if successful or false if it ended on error
*/
public Exit launch( Class mainClass , String ...args ) {
jvmArgs = configureArguments(mainClass,args);
try {
Runtime rt = Runtime.getRuntime();
Process pr = rt.exec(jvmArgs);
// If it exits too quickly it might not get any error messages if it crashes right away
// so the work around is to sleep
Thread.sleep(500);
BufferedReader input = new BufferedReader(new InputStreamReader(pr.getInputStream()));
BufferedReader error = new BufferedReader(new InputStreamReader(pr.getErrorStream()));
// print the output from the slave
if( !monitorSlave(pr, input, error) ) {
if( killRequested )
return Exit.REQUESTED;
else
return Exit.FROZEN;
}
if( pr.exitValue() != 0 ) {
return Exit.RETURN_NOT_ZERO;
} else {
return Exit.NORMAL;
}
} catch (IOException | InterruptedException e) {
throw new RuntimeException(e);
}
}
/**
* Prints printOut the standard printOut and error from the slave and checks its health. Exits if
* the slave has finished or is declared frozen.
*
* @return true if successful or false if it was forced to kill the slave because it was frozen
*/
private boolean monitorSlave(Process pr,
BufferedReader input, BufferedReader error)
throws IOException, InterruptedException {
// flush the input buffer
System.in.skip(System.in.available());
// If the total amount of time allocated to the slave exceeds the maximum number of trials multiplied
// by the maximum runtime plus some fudge factor the slave is declared as frozen
boolean frozen = false;
long startTime = System.currentTimeMillis();
long lastAliveMessage = startTime;
for(;;) {
while( System.in.available() > 0 ) {
if( System.in.read() == 'q' ) {
System.out.println("User requested for the application to quit by pressing 'q'");
System.exit(0);
}
}
printBuffer(error, printErr);
if( input.ready() ) {
printBuffer(input, printOut);
} else {
Thread.sleep(500);
}
try {
// exit value throws an exception is the process has yet to stop
pr.exitValue();
break;
} catch( IllegalThreadStateException e) {
if( killRequested ) {
pr.destroy();
break;
}
// check to see if the process is frozen
if(frozenTime > 0 && System.currentTimeMillis() - startTime > frozenTime ) {
pr.destroy(); // kill the process
frozen = true;
break;
}
// let everyone know its still alive
if( System.currentTimeMillis() - lastAliveMessage > 60000 ) {
System.out.println("\nMaster is still alive: "+new Date()+" Press 'q' and enter to quit.");
lastAliveMessage = System.currentTimeMillis();
}
}
}
printBuffer(error, printErr);
printBuffer(input, printOut);
durationMilli = System.currentTimeMillis()-startTime;
return !frozen && !killRequested;
}
char buffInput[] = new char[1024];
protected void printBuffer(BufferedReader input , PrintStream output ) throws IOException {
int length = 0;
while( input.ready() ) {
int val = input.read();
if( val < 0 ) break;
buffInput[length++] = (char)val;
if( length == buffInput.length ) {
output.print(new String(buffInput,0,length));
length = 0;
}
}
output.print(new String(buffInput,0,length));
}
private String[] configureArguments( Class mainClass , String ...args ) {
String out[] = new String[7+args.length];
String app = System.getProperty("java.home")+"/bin/java";
out[0] = app;
out[1] = "-server";
out[2] = "-Xms"+memoryInMB+"M";
out[3] = "-Xmx"+memoryInMB+"M";
out[4] = "-classpath";
out[5] = classPath;
out[6] = mainClass.getName();
for (int i = 0; i < args.length; i++) {
out[7+i] = args[i];
}
return out;
}
public String getClassPath() {
return classPath;
}
public long getAllocatedMemoryInMB() {
return memoryInMB;
}
public long getFrozenTime() {
return frozenTime;
}
public String[] getArguments() {
return jvmArgs;
}
public void requestKill() {
killRequested = true;
}
public boolean isKillRequested() {
return killRequested;
}
public PrintStream getPrintOut() {
return printOut;
}
public void setPrintOut(PrintStream out) {
this.printOut = out;
}
public PrintStream getPrintErr() {
return printErr;
}
public void setPrintErr(PrintStream err) {
this.printErr = err;
}
public enum Exit
{
/**
* Exited normally.
*/
NORMAL,
/**
* Did not finish in the required amount of time
*/
FROZEN,
/**
* Killed by user
*/
REQUESTED,
/**
* exited with a non zero return value
*/
RETURN_NOT_ZERO,
}
}