boofcv.gui.JavaRuntimeLauncher Maven / Gradle / Ivy
/*
* Copyright (c) 2021, 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 org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.List;
import static boofcv.misc.BoofMiscOps.timeStr;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Class for launching JVMs. Monitors the status and kills frozen threads. Keeps track of execution time and
* sets up class path.
*
* Output and error stream can be changed at any time and is designed to be thread safe.
*
* @author Peter Abeles
*/
@SuppressWarnings({"NullAway.Init"})
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;
private final Object streamLock = new Object();
/**
* 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( @Nullable List 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(), UTF_8));
BufferedReader error = new BufferedReader(new InputStreamReader(pr.getErrorStream(), UTF_8));
// 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);
}
}
synchronized (streamLock) {
printBuffer(error, printErr);
}
if (input.ready()) {
synchronized (streamLock) {
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: " + timeStr() + " Press 'q' and enter to quit.");
lastAliveMessage = System.currentTimeMillis();
}
}
}
synchronized (streamLock) {
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";
int idx = 0;
out[idx++] = app;
out[idx++] = "-server";
if (memoryInMB > 0) {
out[idx++] = "-Xms" + memoryInMB + "M";
out[idx++] = "-Xmx" + memoryInMB + "M";
}
out[idx++] = "-classpath";
out[idx++] = classPath;
out[idx++] = mainClass.getName();
for (int i = 0; i < args.length; i++) {
out[idx++] = args[i];
}
out = Arrays.copyOf(out, idx);
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 ) {
synchronized (streamLock) {
this.printOut = out;
}
}
public PrintStream getPrintErr() {
synchronized (streamLock) {
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,
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy