All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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