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

org.evosuite.utils.ExternalProcessGroupHandler Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2010-2018 Gordon Fraser, Andrea Arcuri and EvoSuite
 * contributors
 *
 * This file is part of EvoSuite.
 *
 * EvoSuite is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published
 * by the Free Software Foundation, either version 3.0 of the License, or
 * (at your option) any later version.
 *
 * EvoSuite 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
 * Lesser Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with EvoSuite. If not, see .
 */
package org.evosuite.utils;

import java.io.*;
import java.rmi.ConnectException;
import java.rmi.RemoteException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.CountDownLatch;

import org.evosuite.ClientProcess;
import org.evosuite.ConsoleProgressBar;
import org.evosuite.Properties;
import org.evosuite.result.TestGenerationResult;
import org.evosuite.result.TestGenerationResultBuilder;
import org.evosuite.rmi.MasterServices;
import org.evosuite.rmi.service.ClientNodeRemote;
import org.evosuite.rmi.service.ClientState;
import org.evosuite.runtime.sandbox.Sandbox;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import sun.misc.Signal;
import sun.misc.SignalHandler;

/*
 * this code should be used by the main master process.
 * 
 * FIXME: once RMI is stable tested, we ll need to remove all the TCP stuff, and refactor
 */

public class ExternalProcessGroupHandler {
	/** Constant logger */
	protected static final Logger logger = LoggerFactory.getLogger(ExternalProcessGroupHandler.class);

	protected Process[] processGroup;
	protected String[][] last_commands;

	protected Thread[] output_printers;
	protected Thread[] error_printers;
	protected Thread[] message_handlers;

	protected ObjectInputStream in;

	protected Object final_result;
	/** Constant WAITING_FOR_DATA */
	protected static final Object WAITING_FOR_DATA = "waiting_for_data_"
			+ System.currentTimeMillis();

	protected Thread[] processKillHooks;
	protected Thread clientRunningOnThread;

	protected volatile CountDownLatch[] latches;

	protected String base_dir = System.getProperty("user.dir");

	private String[] hsErrFiles;

	public ExternalProcessGroupHandler() {
		this(1);
	}

	/**
	 * 

* Constructor for ExternalProcessGroupHandler. *

*/ public ExternalProcessGroupHandler(final int nrOfProcesses) { this.processGroup = new Process[nrOfProcesses]; this.last_commands = new String[nrOfProcesses][]; this.output_printers = new Thread[nrOfProcesses]; this.error_printers = new Thread[nrOfProcesses]; this.message_handlers = new Thread[nrOfProcesses]; this.processKillHooks = new Thread[nrOfProcesses]; this.latches = new CountDownLatch[nrOfProcesses]; this.hsErrFiles = new String[nrOfProcesses]; } /** * Only for debug reasons. * @param ms */ public void stopAndWaitForClientOnThread(long ms){ if(clientRunningOnThread != null && clientRunningOnThread.isAlive()){ clientRunningOnThread.interrupt(); } long start = System.currentTimeMillis(); while( (System.currentTimeMillis() - start) < ms) { //to avoid miss it in case of interrupt if(clientRunningOnThread != null && clientRunningOnThread.isAlive()){ try { clientRunningOnThread.join(ms - (System.currentTimeMillis() - start)); break; } catch (InterruptedException e) { } } else { break; } } if( clientRunningOnThread != null && clientRunningOnThread.isAlive()) { throw new AssertionError( "clientRunningOnThread is alive even after waiting "+ms+"ms"); } } /** * Sets the base directory. * * @param base_dir * the base directory */ public void setBaseDir(String base_dir) { this.base_dir = base_dir; } /** *

* startProcess *

* * @param command * an array of {@link java.lang.String} objects. * @return a boolean. */ public boolean startProcess(String[] commands) { List l_commands = new ArrayList(); l_commands.add(commands); return this.startProcessGroup(l_commands); } /** * Starts a process for each command array of the given list. If one process fails to start, all already started * processes are killed. * * @param commands * a list of arrays of commands to start * @return true iff all processes have started correctly, false otherwise */ public boolean startProcessGroup(List commands) { int rollbackToI = 0; for (int i = 0; i < commands.size(); i++) { String[] command = commands.get(i); if(! Properties.IS_RUNNING_A_SYSTEM_TEST) { logger.debug("Going to start process with command: " + Arrays.toString(command).replace(",", " ")); } List formatted = new LinkedList<>(); for(String s : command){ String token = s.trim(); if(!token.isEmpty()){ formatted.add(token); } } hsErrFiles[i] = "hs_err_EvoSuite_client_p" + getServerPort() + "_t" + System.currentTimeMillis(); String option = "-XX:ErrorFile=" + hsErrFiles[i]; formatted.add(1,option); // add it after the first "java" command if (!startProcess(formatted.toArray(new String[0]), i, null)) { rollbackToI = i; break; } } if (rollbackToI > 0) { for (int i = 0; i < rollbackToI; i++) { killProcess(i); } } return rollbackToI == 0; } protected boolean didClientJVMCrash(final int processIndex){ return new File(hsErrFiles[processIndex]).exists(); } protected String getAndDeleteHsErrFile(final int processIndex){ if(!didClientJVMCrash(processIndex)){ return null; } StringBuilder builder = new StringBuilder(); File file = new File(hsErrFiles[processIndex]); file.deleteOnExit(); try(Scanner in = new Scanner(file);) { while (in.hasNextLine()) { String row = in.nextLine(); //do not read the full file, just the header if(row.startsWith("#")){ builder.append(row).append("\n"); } else { break; //end of the header } } } catch (FileNotFoundException e) { //shouldn't really happen logger.error("Error while reading "+file.getAbsolutePath() + ": "+e.getMessage()); return null; } return builder.toString(); } public String getProcessStates(){ if(processGroup == null){ return "null"; } StringBuilder builder = new StringBuilder(); for (int i = 0; i < processGroup.length; i++) { builder.append("process nr. "); builder.append(i); builder.append(": "); if(processGroup[i] == null){ builder.append("null\n"); } else { try{ int exitValue = processGroup[i].exitValue(); builder.append("Terminated with exit status "); builder.append(exitValue); builder.append("\n"); } catch(IllegalThreadStateException e){ builder.append("Still running\n"); } } } return builder.toString(); } /** *

* startProcess *

* * @param command * an array of {@link java.lang.String} objects. * @param population_data * a {@link java.lang.Object} object. * @return a boolean. */ protected boolean startProcess(String[] command, int processIndex, Object population_data) { if (processGroup[processIndex] != null) { logger.warn("Already running an external process"); return false; } latches[processIndex] = new CountDownLatch(1); final_result = WAITING_FOR_DATA; //the following thread is important to make sure that the external process is killed //when current process ends processKillHooks[processIndex] = new Thread() { @Override public void run() { killProcess(processIndex); closeServer(); } }; Runtime.getRuntime().addShutdownHook(processKillHooks[processIndex]); // now start the process if (!Properties.CLIENT_ON_THREAD) { File dir = new File(base_dir); ProcessBuilder builder = new ProcessBuilder(command); builder.directory(dir); builder.redirectErrorStream(false); try { processGroup[processIndex] = builder.start(); } catch (IOException e) { logger.error("Failed to start external process", e); return false; } startExternalProcessPrinter(processIndex); } else { /* * Here we run client on a thread instead of process. * NOTE: this should only be done for debugging, ie in * JUnit files created for testing EvoSuite. */ clientRunningOnThread = new Thread() { @Override public void run() { /* * NOTE: the handling of the parameters "-D" should be handled * directly in JUnit by setting the different values in Properties */ ClientProcess.main(new String[0]); } }; clientRunningOnThread.setName("client"); clientRunningOnThread.start(); Sandbox.addPrivilegedThread(clientRunningOnThread); } startSignalHandler(processIndex); last_commands[processIndex] = command; return true; } /** *

* killProcess *

*/ public void killProcess() { this.killProcess(0); } /** * Terminates the process with given index. * * @param processIndex * index of process to kill */ public void killProcess(final int processIndex) { if (processGroup[processIndex] == null) { return; } try { Runtime.getRuntime().removeShutdownHook(processKillHooks[processIndex]); } catch (Exception e) { /* do nothing. this can happen if shutdown is in progress */ } /* * TODO: use RMI to 'gracefully' stop the client */ if (processGroup[processIndex] != null) { try { //be sure streamers are closed, otherwise process might hang on Windows processGroup[processIndex].getOutputStream().close(); processGroup[processIndex].getInputStream().close(); processGroup[processIndex].getErrorStream().close(); } catch (Exception t) { logger.error("Failed to close process stream: " + t.toString()); } processGroup[processIndex].destroy(); } processGroup[processIndex] = null; if (clientRunningOnThread != null && clientRunningOnThread.isAlive()) { clientRunningOnThread.interrupt(); } clientRunningOnThread = null; if (output_printers[processIndex] != null && output_printers[processIndex].isAlive()) output_printers[processIndex].interrupt(); output_printers[processIndex] = null; if (error_printers[processIndex] != null && error_printers[processIndex].isAlive()) error_printers[processIndex].interrupt(); error_printers[processIndex] = null; if (message_handlers[processIndex] != null && message_handlers[processIndex].isAlive()) { message_handlers[processIndex].interrupt(); } message_handlers[processIndex] = null; } /** * Terminates all running processes. */ public void killAllProcesses() { for (int i = 0; i < processGroup.length; i++) { killProcess(i); } } /** *

* getServerPort *

* * @return a int. */ public int getServerPort() { return MasterServices.getInstance().getRegistryPort(); } /** *

* openServer *

* * @return a int. */ public int openServer() { boolean started = MasterServices.getInstance().startRegistry(); if(!started){ logger.error("Not possible to start RMI registry"); return -1; } try { MasterServices.getInstance().registerServices(); } catch (RemoteException e) { logger.error("Failed to start RMI services",e); return -1; } return MasterServices.getInstance().getRegistryPort(); /* if (server == null) { try { server = new ServerSocket(); server.setSoTimeout(10000); server.bind(null); return server.getLocalPort(); } catch (Exception e) { logger.error("Not possible to start TCP server", e); } } return -1; */ } /** *

* closeServer *

*/ public void closeServer() { MasterServices.getInstance().stopServices(); } /** *

* startExternalProcessPrinter *

* * @param processIndex * index of process */ protected void startExternalProcessPrinter(final int processIndex) { if (output_printers[processIndex] == null || !output_printers[processIndex].isAlive()) { output_printers[processIndex] = new Thread() { @Override public void run() { try { BufferedReader proc_in = new BufferedReader( new InputStreamReader(processGroup[processIndex].getInputStream())); int data = 0; while (data != -1 && !isInterrupted()) { data = proc_in.read(); if (data != -1 && Properties.PRINT_TO_SYSTEM) { System.out.print((char) data); } } } catch (Exception e) { if(MasterServices.getInstance().getMasterNode() == null) return; boolean finished = true; for(ClientState state : MasterServices.getInstance().getMasterNode().getCurrentState()) { if(state != ClientState.DONE) { finished = false; break; } } if(!finished) logger.error("Exception while reading output of client process. " + e.getMessage()); else logger.debug("Exception while reading output of client process. " + e.getMessage()); } } }; output_printers[processIndex].start(); } if (error_printers[processIndex] == null || !error_printers[processIndex].isAlive()) { error_printers[processIndex] = new Thread() { @Override public void run() { try { BufferedReader proc_in = new BufferedReader( new InputStreamReader(processGroup[processIndex].getErrorStream())); int data = 0; String errorLine =""; while (data != -1 && !isInterrupted()) { data = proc_in.read(); if (data != -1 && Properties.PRINT_TO_SYSTEM) { System.err.print((char) data); errorLine += (char) data; if((char)data == '\n'){ logger.error(errorLine); errorLine = ""; } } } } catch (Exception e) { if(MasterServices.getInstance().getMasterNode() == null) return; boolean finished = true; for(ClientState state : MasterServices.getInstance().getMasterNode().getCurrentState()) { if(state != ClientState.DONE) { finished = false; break; } } if(!finished) logger.error("Exception while reading output of client process. " + e.getMessage()); else logger.debug("Exception while reading output of client process. " + e.getMessage()); } } }; error_printers[processIndex].start(); } if (Properties.SHOW_PROGRESS && (Properties.LOG_LEVEL==null || (!Properties.LOG_LEVEL.equals("info") && !Properties.LOG_LEVEL.equals("debug") && !Properties.LOG_LEVEL.equals("trace")) ) ) { ConsoleProgressBar.startProgressBar(); } } /** *

* startExternalProcessMessageHandler *

* * @param processIndex * index of process */ protected void startExternalProcessMessageHandler(final int processIndex) { if (message_handlers[processIndex] != null && message_handlers[processIndex].isAlive()) return; message_handlers[processIndex] = new Thread() { @Override public void run() { boolean read = true; while (read && !isInterrupted()) { String message = null; Object data = null; try { message = (String) in.readObject(); data = in.readObject(); logger.debug("Received msg: " + message); logger.debug("Received data: " + data); } catch (Exception e) { /* * TODO: this parts need to be improved. * An exception here is most likely due to the client crashing. * If there is still enough budget (this might not be trivial to check, * eg it could be fine for time, but not number of fitness evaluations), then * we should try to re-start based on the partial info received so far, eg * the best solutions found so far which was sent to master */ logger.error("Class " + Properties.TARGET_CLASS + ". Error when reading message. Likely the client has crashed. Error message: " + e.getMessage()); message = Messages.FINISHED_COMPUTATION; data = null; } if (message.equals(Messages.FINISHED_COMPUTATION)) { LoggingUtils.getEvoLogger().info("* Computation finished"); read = false; killProcess(processIndex); final_result = data; latches[processIndex].countDown(); } else if (message.equals(Messages.NEED_RESTART)) { //now data represent the current generation LoggingUtils.getEvoLogger().info("* Restarting client process"); killProcess(processIndex); /* * TODO: this will need to be changed, to take into account * a possible reduced budget */ startProcess(last_commands[processIndex], processIndex, data); } else { killProcess(processIndex); logger.error("Class " + Properties.TARGET_CLASS + ". Error, received invalid message: ", message); return; } } } }; message_handlers[processIndex].start(); } /** * Starts the signal handler for process with given index. * * @param processIndex index of process */ protected void startSignalHandler(final int processIndex) { Signal.handle(new Signal("INT"), new SignalHandler() { private boolean interrupted = false; @Override public void handle(Signal arg0) { if (interrupted) System.exit(0); try { interrupted = true; if (processGroup[processIndex] != null) processGroup[processIndex].waitFor(); } catch (InterruptedException e) { logger.warn("",e); } } }); } /** *

* waitForResult *

* * @param timeout * a int. * @return a {@link java.lang.Object} object. */ public TestGenerationResult waitForResult(int timeout) { try { long start = System.currentTimeMillis(); Map clients = MasterServices.getInstance() .getMasterNode().getClientsOnceAllConnected(timeout); if(clients==null){ logger.error("Could not access client process"); return TestGenerationResultBuilder.buildErrorResult("Could not access client process"); } for (Entry entry : clients.entrySet()) { long passed = System.currentTimeMillis() - start; long remaining = timeout - passed; if(remaining <=0 ){ remaining = 1;} boolean finished = false; ClientState clientState = MasterServices.getInstance().getMasterNode().getCurrentState(entry.getKey()); if (!clientState.equals(ClientState.FINISHED)) { try { finished = entry.getValue().waitUntilFinished(remaining); } catch (ConnectException e) { logger.warn("Failed to connect to client. Client with id " + entry.getKey() + " is already finished."); finished = true; } } else { finished = true; } if(!finished){ /* * TODO what to do here? Try to stop the client through RMI? * Or check in which state it is, and based on that decide if giving more time? */ logger.error("Class "+ Properties.TARGET_CLASS+". Clients have not finished yet, although a timeout occurred.\n"+MasterServices.getInstance().getMasterNode().getSummaryOfClientStatuses()); } } } catch (InterruptedException e) { } catch(RemoteException e){ String msg = "Class "+ Properties.TARGET_CLASS+". Lost connection with clients.\n"+MasterServices.getInstance().getMasterNode().getSummaryOfClientStatuses(); boolean crashOccurred = false; for (int i = 0; i < processGroup.length; i++) { if(didClientJVMCrash(i)){ String err = getAndDeleteHsErrFile(i); String clientMsg = "The JVM of the client process crashed:\n"+err; logger.error(clientMsg); crashOccurred = true; } } if (crashOccurred) { logger.error(msg, e); } } for (int i = 0; i < processGroup.length; i++) { killProcess(i); } LoggingUtils.getEvoLogger().info("* Computation finished"); return null; //TODO refactoring /* try { latch.await(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { logger.warn("Class " + Properties.TARGET_CLASS + ". Thread interrupted while waiting for results from client process", e); } return final_result; */ } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy