/*
* Copyright (C) 2006-2016 DLR, Germany
*
* All rights reserved
*
* http://www.rcenvironment.de/
*/
package de.rcenvironment.components.optimizer.common.execution;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.exec.OS;
import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.rcenvironment.components.optimizer.common.OptimizerComponentConstants;
import de.rcenvironment.components.optimizer.common.OptimizerComponentHistoryDataItem;
import de.rcenvironment.core.component.api.ComponentException;
import de.rcenvironment.core.component.execution.api.ComponentContext;
import de.rcenvironment.core.component.execution.api.ConsoleRow;
import de.rcenvironment.core.component.execution.api.ConsoleRowUtils;
import de.rcenvironment.core.datamodel.api.DataType;
import de.rcenvironment.core.datamodel.api.TypedDatum;
import de.rcenvironment.core.datamodel.api.TypedDatumFactory;
import de.rcenvironment.core.datamodel.api.TypedDatumService;
import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;
import de.rcenvironment.core.utils.common.TempFileServiceAccess;
import de.rcenvironment.core.utils.common.textstream.TextStreamWatcher;
import de.rcenvironment.core.utils.executor.LocalApacheCommandLineExecutor;
import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription;
/**
* Class to provide running an external program that has to be blocked for a RCE wf calculation.
*
* @author Sascha Zur
*/
public abstract class OptimizerAlgorithmExecutor implements Runnable {
protected static final Log LOGGER = LogFactory.getLog(OptimizerAlgorithmExecutor.class);
protected static final int SLEEPTIME = 10;
private static final int SOCKET_TIMEOUT = 0;
protected TypedDatumFactory typedDatumFactory;
protected File workingDir;
protected String inputFileEnding;
protected ClientMessage messageFromClient;
protected String port;
protected String inputFileName;
protected ComponentContext compContext;
protected AtomicBoolean initFailed = new AtomicBoolean(false);
protected AtomicBoolean startFailed = new AtomicBoolean(false);
protected Exception startFailedException = null;
protected Map<Integer, Map<String, Double>> iterationData;
private LocalApacheCommandLineExecutor executor;
private Boolean stop;
private Runnable serverThread;
private String outputFilename;
private ServerSocket serverSocket;
private Socket client;
private boolean initializationLoop;
private final Object lockObject = new Object();
public OptimizerAlgorithmExecutor() {
}
public OptimizerAlgorithmExecutor(ComponentContext ci, String newInputFileName, String outputFilename) throws ComponentException {
stop = false;
inputFileName = newInputFileName;
this.outputFilename = outputFilename;
this.compContext = ci;
// the fragment bundle with the binaries should not have this executor bundle as host
// because of the build process. Instead, the Optimizer.common bundle is host and so
// the classpath of common + fragment are merged. For getting the resources stream a
// class from the common bundle (here the CommonBundleClasspathStub) is needed.
try (InputStream jarInput = OptimizerAlgorithmExecutor.class.getResourceAsStream(
"/resources/de.rcenvironment.components.optimizer.simulator.jar")) {
workingDir = TempFileServiceAccess.getInstance().createManagedTempDir();
File jar = new File(workingDir.getAbsolutePath() + File.separator + "de.rcenvironment.components.optimizer.simulator.jar");
FileUtils.copyInputStreamToFile(jarInput, jar);
jar.setExecutable(true);
} catch (IOException e) {
throw new ComponentException("Failed to setup optimizer", e);
}
}
protected abstract void prepareProblem() throws ComponentException;
/**
* Reads the output of the optimizer. The values are used in the RCE workflow.
*
* @param outputValues that are read.
* @throws IOException reading the file
*/
public abstract void readOutputFileFromExternalProgram(Map<String, TypedDatum> outputValues) throws IOException;
protected abstract void writeInputFileforExternalProgram(Map<String, Double> inputVariables,
Map<String, Double> inputVariablesGradients,
Map<String, Double> constraintVariables,
String outputFileName) throws IOException;
/**
* Returns the optimum for the last optimization run, if the run was successful.
*
* @return the evaluation run number of the optimal design
* @throws ComponentException if evaluation run number could not be figured out
*/
public abstract int getOptimalRunNumber() throws ComponentException;
@Override
@TaskDescription("Optimizer Algorithm Executor")
public abstract void run();
private void writePortFile() throws ComponentException {
try {
File portFile = new File(workingDir.getAbsolutePath() + File.separator + inputFileName + ".port");
List<String> lines = new LinkedList<>();
lines.add("" + port);
FileUtils.writeLines(portFile, lines, System.getProperty("line.separator"));
} catch (IOException e) {
throw new ComponentException("Failed to setup optimizer (failed to write port file)", e);
}
}
/**
* Needed for the initialization loop.
*
* @throws ComponentException if init fails
* @return true, if loop was successful
*/
public boolean initializationLoop() throws ComponentException {
initializationLoop = true;
boolean returnValue = false;
try {
if (!isStopped()) {
runNewServer();
while (client == null) {
try {
// TODO what is the reason to wait arbitrary? 10 msec here?
Thread.sleep(SLEEPTIME);
} catch (InterruptedException e) {
LOGGER.error("Failed to wait for optimizer to finish setup", e);
}
if (initFailed.get()) {
throw (ComponentException) startFailedException;
}
if (startFailed.get()) {
break;
}
}
if (client != null) {
returnValue = readMessageFromClient();
}
}
} catch (IOException e) {
throw new ComponentException("Failed to setup optimizer", e);
}
initializationLoop = false;
return returnValue && !isStopped() && !isInitFailed() && !getStartFailed().get();
}
/**
* Starts the program that shall be blocked.
*
* @param executionCommand : to execute
* @throws ComponentException
*/
protected void startProgram(String executionCommand) throws ComponentException {
startProgram(executionCommand, "");
}
/**
* Starts the program that shall be blocked.
*
* @param executionCommand : to execute
* @throws ComponentException
*/
protected void startProgram(String executionCommand, String previousCommand) throws ComponentException {
prepareProblem();
if (!initializationLoop) {
startServer();
}
try {
String javaHome = System.getProperty("java.home");
if (!javaHome.contains("bin")) {
javaHome += File.separator + "bin";
}
File javaHomefile = new File(workingDir.getAbsolutePath() + File.separator + "javaHome.txt");
FileWriter fw = new FileWriter(javaHomefile);
fw.write("" + javaHome);
fw.close();
String command = "";
if (previousCommand != null && previousCommand.equals("[INTEGRATED OPTIMIZER: ENTER PATH TO PYTHON EXECUTABLE HERE]")) {
throw new ComponentException(
"Generic optimizer python path not properly configured."
+ " Please set the path in the \"python_path\" file in the integration folder.");
}
if (OS.isFamilyUnix()) {
if (previousCommand != null && previousCommand.equalsIgnoreCase("")) {
command = executionCommand + inputFileName;
} else {
command =
previousCommand + " \"" + workingDir.getAbsolutePath() + File.separator + executionCommand + "\" "
+ inputFileName;
}
} else if (previousCommand == null || previousCommand.equalsIgnoreCase("")) {
command = executionCommand + inputFileName;
} else {
command = previousCommand + " " + executionCommand;
}
executor = new LocalApacheCommandLineExecutor(new File(workingDir.getAbsolutePath() + File.separator));
executor.start(command);
executor.setEnv("PATH", System.getenv("PATH") + File.pathSeparator + javaHome);
File consoleStdOutput = new File(workingDir, "consoleStdOutput.txt");
File consoleErrOutput = new File(workingDir, "consoleErrOutput.txt");
TextStreamWatcher stdOutWatcher = ConsoleRowUtils.logToWorkflowConsole(compContext.getLog(), executor.getStdout(),
ConsoleRow.Type.TOOL_OUT, consoleStdOutput, false);
TextStreamWatcher stdErrWatcher = ConsoleRowUtils.logToWorkflowConsole(compContext.getLog(), executor.getStderr(),
ConsoleRow.Type.TOOL_ERROR, consoleErrOutput, false);
try {
int exitCode = executor.waitForTermination();
if (exitCode != 0) {
if (!initFailed.get()) {
startFailed.set(true);
startFailedException = new ComponentException("Optimizer exited with a non zero exit code. "
+ "Optimizer exit code = " + exitCode);
}
}
stdOutWatcher.waitForTermination();
stdErrWatcher.waitForTermination();
stop();
} catch (InterruptedException e) {
LOGGER.info("Failed to wait for optimizer to wake up (optimizer ended abruptly).", e);
}
} catch (IOException e) {
throw new ComponentException("Failed to start optimizer", e);
}
}
/**
* Runs a single optimization step.
*
* @param inputVariables : All target functions
* @param inputVariablesGradients : all gradients for the target functions
* @param constraintVariables : all constraints
* @param constraintVariablesGradients : all gradients for the constraints
* @param outputValues : all design variables
* @throws ComponentException on unexpected errors
*/
public void runStep(Map<String, Double> inputVariables, Map<String, Double> inputVariablesGradients,
Map<String, Double> constraintVariables, Map<String, Double> constraintVariablesGradients,
Map<String, TypedDatum> outputValues) throws ComponentException {
try {
if (!isStopped()) {
if (messageFromClient != null) {
writeInputFileforExternalProgram(inputVariables, inputVariablesGradients,
constraintVariables, outputFilename);
sendMessageToClient("Close");
serverThread = runNewServer();
// Wait for client to connect or termination of program thread
while (client == null && !isStopped()) {
try {
// TODO what is the reason to wait arbitrary? 10 msec here?
Thread.sleep(SLEEPTIME);
} catch (InterruptedException e) {
LOGGER.error("Failed to wait for optimizer to finish running optimization step", e);
}
}
if (!stop) {
if (readMessageFromClient()) {
readOutputFileFromExternalProgram(outputValues);
}
}
}
}
} catch (IOException e) {
throw new ComponentException("Failed to run optimization step", e);
}
}
private void sendMessageToClient(String message) throws IOException {
if (client != null && !client.isClosed()) {
PrintWriter printWriter =
new PrintWriter(
new OutputStreamWriter(
client.getOutputStream()));
printWriter.print(message);
printWriter.flush();
client.close();
client = null;
}
}
private boolean readMessageFromClient() throws IOException {
if (!client.isClosed()) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(client.getInputStream()));
final int buffersize = 1024;
char[] buffer = new char[buffersize];
int blockLength = bufferedReader.read(buffer, 0, buffersize);
String nachricht = new String(buffer, 0, blockLength);
if (!nachricht.equals("exit")) {
String[] splitMessage = nachricht.split("&&");
this.messageFromClient = new ClientMessage(splitMessage[0], splitMessage[1]);
return true;
}
}
return false;
}
private void startServer() throws ComponentException {
if (!stop) {
try {
if (port == null || port.equals("")) {
serverSocket = new ServerSocket(0);
port = "" + serverSocket.getLocalPort();
} else {
serverSocket = new ServerSocket(Integer.valueOf(port));
}
serverSocket.setSoTimeout(SOCKET_TIMEOUT);
writePortFile();
} catch (IOException e) {
throw new ComponentException("Failed to start the server needed to run the optimizer", e);
}
}
}
private Runnable runNewServer() throws ComponentException {
if (serverSocket == null) {
startServer();
}
Runnable newServerThread = new Runnable() {
@Override
@TaskDescription("Optimizer Server Socket")
public void run() {
try {
if (serverSocket != null) {
client = serverSocket.accept();
}
} catch (IOException e) {
if (isStopped()) {
LOGGER.debug("Socket closed because program finished");
} else {
// TODO review error handling; I expect an exception to be thrown here
// added at least logging, but only logging the error might not sufficient
// here
LOGGER.error("Failed to run the server needed to run the optimizer", e);
}
}
}
};
ConcurrencyUtils.getAsyncTaskService().execute(newServerThread);
return newServerThread;
}
/**
* Stops everything.
*/
public void stop() {
synchronized (lockObject) {
stop = true;
}
try {
if (serverThread != null) {
synchronized (serverThread) {
if (port != null && serverSocket != null) {
Socket server = new Socket("localhost", Integer.parseInt(port));
PrintWriter printWriter =
new PrintWriter(
new OutputStreamWriter(
server.getOutputStream()));
printWriter.print("exit");
printWriter.flush();
server.close();
}
}
} else {
startFailed.set(true);
}
} catch (IOException e) {
LOGGER.warn("Optimizer connection reset (maybe because of through canceling the workflow).");
}
}
/**
* Terminates the connection to the client (jar blocker).
*/
public void closeConnection() {
try {
sendMessageToClient("Close");
if (executor != null) {
executor.waitForTermination();
}
} catch (IOException e) {
LOGGER.error("Error on shutting down the optimizer", e);
} catch (InterruptedException e) {
LOGGER.error("Error on shutting down the optimizer", e);
}
}
/**
* Gets rid of all tmp files.
*/
public void dispose() {
try {
if (client != null) {
this.client.close();
}
if (this.serverSocket != null) {
this.serverSocket.close();
}
if (executor != null) {
executor.waitForTermination();
}
} catch (IOException e) {
LOGGER.error("Failed to dispose blocker files", e);
} catch (InterruptedException e) {
LOGGER.error("Failed to dispose blocker files", e);
}
}
public String getPort() {
return port;
}
/**
*
* @return true, if optimizer is stopped
*/
public boolean isStopped() {
synchronized (lockObject) {
return stop;
}
}
/**
* Helper class to pass the information from the jar file.
*
* @author Sascha Zur
*/
public class ClientMessage {
private String currentWorkingDir;
private String filename;
public ClientMessage(String currentWorkingDir, String filename) {
super();
this.setCurrentWorkingDir(currentWorkingDir);
this.setFilename(filename);
}
public String getCurrentWorkingDir() {
return currentWorkingDir;
}
public void setCurrentWorkingDir(String currentWorkingDir) {
this.currentWorkingDir = currentWorkingDir;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
}
protected int countInput(Collection<String> input) {
int result = 0;
for (String e : input) {
if (compContext.getDynamicInputIdentifier(e).equals(OptimizerComponentConstants.ID_OBJECTIVE)
&& !e.contains(OptimizerComponentConstants.GRADIENT_DELTA)) {
if (compContext.getInputDataType(e) == DataType.Vector) {
result += Integer.parseInt(compContext.getInputMetaDataValue(e, OptimizerComponentConstants.METADATA_VECTOR_SIZE));
} else {
result++;
}
}
}
return result;
}
protected int countConstraint(Collection<String> input) {
int result = 0;
for (String e : input) {
if (compContext.getDynamicInputIdentifier(e).equals(OptimizerComponentConstants.ID_CONSTRAINT)
&& !e.contains(OptimizerComponentConstants.GRADIENT_DELTA)) {
if (compContext.getInputDataType(e) == DataType.Vector) {
result += Integer.parseInt(compContext.getInputMetaDataValue(e, OptimizerComponentConstants.METADATA_VECTOR_SIZE));
} else {
result++;
}
}
}
return result;
}
protected void bindTypedDatumService(TypedDatumService newTypedDatumService) {
typedDatumFactory = newTypedDatumService.getFactory();
}
protected void unbindTypedDatumService(TypedDatumService oldTypedDatumService) {}
public boolean isInitFailed() {
return initFailed.get();
}
/**
* Checks if for the current run derivatives are needed from the workflow.
*
* @return true, if they are needed.
*/
public abstract boolean getDerivativedNeeded();
/**
* Individual writing the history data item.
*
* @param historyItem to write
*/
public abstract void writeHistoryDataItem(OptimizerComponentHistoryDataItem historyItem);
public File getWorkingDir() {
return workingDir;
}
public AtomicBoolean getStartFailed() {
return startFailed;
}
public void setStartFailed(AtomicBoolean startFailed) {
this.startFailed = startFailed;
}
public void setWorkingDir(File workingDir) {
this.workingDir = workingDir;
}
public void setIterationData(Map<Integer, Map<String, Double>> iterationData) {
this.iterationData = iterationData;
}
public Throwable getStartFailedException() {
return startFailedException;
}
}