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

org.evosuite.continuous.job.JobExecutor 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.continuous.job;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.evosuite.Properties;
import org.evosuite.continuous.CtgConfiguration;
import org.evosuite.continuous.persistency.StorageManager;
import org.evosuite.utils.LoggingUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Job executor will run EvoSuite on separate processes.
 * There will be no communication with these masters/clients, whose
 * visible side-effects would only be files written on local disk.
 * This does simplify the architecture a lot, especially considering we can 
 * have several instances running in parallel.
 * Furthermore, writing to disk at each search has benefit that we can recover from
 * premature crashes, reboot of machine, etc.
 * This is particularly useful considering that CTG can be left running for hours, if not
 * even days. 
 * Downside is not a big deal, as the searches in a schedule are anyway run independently. 
 * 
 * 
 * 

* Note: under no case ever two different jobs should access the same files at the same time, even * if just for reading. Piece-of-crap OS like Windows do put locks on files based * on processes accessing them... for multi-process applications running on same host, * that is a recipe for disaster... * * @author arcuri * */ public class JobExecutor { private static Logger logger = LoggerFactory.getLogger(JobExecutor.class); private volatile boolean executing; private long startTimeInMs; /** * This used to wait till all jobs are finished running */ private volatile CountDownLatch latch; /** * Several threads read from this queue to execute jobs * on separated process */ private BlockingQueue jobQueue; /** * keep track of all the jobs that have been executed so far. * Each job definition (value) is indexed by the CUT name (key). * This assumes in a schedule that the CUT names are unique, ie, * no more than one job should exist for the same CUT */ private Map finishedJobs; protected final CtgConfiguration configuration; private String projectClassPath; private StorageManager storage; /** * Main constructor * */ public JobExecutor(StorageManager storage, String projectClassPath, CtgConfiguration conf) throws IllegalArgumentException{ this.storage = storage; if (!storage.isStorageOk()) { throw new IllegalArgumentException("Storage is not initialized"); } this.configuration = conf; this.projectClassPath = projectClassPath; } protected long getRemainingTimeInMs(){ long elapsed = System.currentTimeMillis() - startTimeInMs; long budgetInMs = configuration.timeInMinutes * 60 * 1000; long remaining = budgetInMs - elapsed; return remaining; } /** * Do a separate search with EvoSuite for all jobs in the given list. * The executor tries a best effort to execute the jobs in the given order, * although no guarantee is provided (eg, there might be dependencies among jobs). * * @param jobs * @throws IllegalStateException if we are already executing some jobs */ public synchronized void executeJobs(final List jobs, final int cores) throws IllegalStateException{ if(executing){ throw new IllegalStateException("Already executing jobs"); } logger.info("Going to execute "+jobs.size()+" jobs"); initExecution(jobs); Thread mainThread = new Thread(){ @Override public void run(){ JobHandler[] handlers = JobHandler.getPool(cores,JobExecutor.this); for(JobHandler handler : handlers){ handler.start(); } long longestJob = -1l; try{ LoggingUtils.getEvoLogger().info("Going to execute "+jobs.size()+" jobs"); long minutes = configuration.timeInMinutes; LocalDateTime endBy = LocalDateTime.now().plus(minutes, ChronoUnit.MINUTES); LoggingUtils.getEvoLogger().info("Estimated completion time: "+minutes+" minutes, by "+endBy); longestJob = execute(jobs); } catch(Exception e){ logger.error("Error while trying to execute the "+jobs.size()+" jobs: "+e.getMessage(),e); } finally { /* * When we arrive here, in the worst case each handler is still executing a job, * plus one in the queue. * Note: this check is not precise */ if(!this.isInterrupted() && longestJob > 0){ try { latch.await((longestJob*2) + (60000),TimeUnit.MILLISECONDS); } catch (InterruptedException e) { this.interrupt(); } } //be sure to release the latch long stillNotFinished = latch.getCount(); for(int i=0; i jobs) { executing = true; startTimeInMs = System.currentTimeMillis(); latch = new CountDownLatch(jobs.size()); /* * there is a good reason to have a blocking queue of size 1. * we want to put jobs on the queue only when we know there is going * to be a handler that can pull it. * this helps the scheduler, as we can wait longer before making the decision * of what job to schedule next */ jobQueue = new ArrayBlockingQueue<>(1); finishedJobs = new ConcurrentHashMap<>(); } protected long execute(List jobs){ long longestJob = -1l; //TODO handle memory Queue toExecute = new LinkedList<>(); toExecute.addAll(jobs); List postponed = new LinkedList<>(); mainLoop: while(!toExecute.isEmpty() || !postponed.isEmpty()){ long remaining = getRemainingTimeInMs(); if(remaining <= 0){ //time is over. do not submit any more job break mainLoop; } JobDefinition chosenJob = null; //postponed jobs have the priority if(!postponed.isEmpty()){ Iterator iterator = postponed.iterator(); postponedLoop : while(iterator.hasNext()){ JobDefinition job = iterator.next(); if(job.areDependenciesSatisfied(jobs,finishedJobs.keySet())){ chosenJob = job; iterator.remove(); break postponedLoop; } } } if(chosenJob == null && toExecute.isEmpty()){ assert !postponed.isEmpty(); /* * tricky case: the are no more jobs in the queue, and there are * jobs that have been postponed. But, at the moment, we shouldn't * execute them due to missing dependencies. * * As the dependencies are just "optimizations" (eg, seeding), it is not * wrong to execute any of those postponed jobs. * There might be useful heuristics to pick up one in a smart way but, * for now, we just choose the first (and so oldest) */ chosenJob = postponed.remove((int) 0); //it is important to get the oldest jobs } if(chosenJob == null){ assert !toExecute.isEmpty(); toExecuteLoop : while(!toExecute.isEmpty()){ JobDefinition job = toExecute.poll(); if(job.areDependenciesSatisfied(jobs,finishedJobs.keySet())){ chosenJob = job; break toExecuteLoop; } else { postponed.add(job); } } if(chosenJob == null){ /* * yet another tricky situation: we started with a list of jobs to execute, * and none in the postponed list; but we cannot execute any of those * jobs (this could happen if they all depend on jobs that are currently running). * We should hence just choose one of them. Easiest thing, and most clean, * to do is just to go back to beginning of the loop */ assert !postponed.isEmpty() && toExecute.isEmpty(); continue mainLoop; } } assert chosenJob != null; longestJob = Math.max(longestJob, chosenJob.seconds * 1000); try { jobQueue.offer(chosenJob, remaining, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { Thread.currentThread().interrupt(); //important for check later break mainLoop; } } return longestJob; } public JobDefinition pollJob() throws InterruptedException{ return jobQueue.take(); } public void doneWithJob(JobDefinition job){ finishedJobs.put(job.cut, job); latch.countDown(); LoggingUtils.getEvoLogger().info("Completed job. Left: "+latch.getCount()); } public void waitForJobs() { /* * Note: this method could be called a long while after the starting * of the execution. But likely not so important to handle such situation. * * Furthermore, due to crashes and phases that could end earlier (eg, minimization * and assertion generation), it is likely that jobs will finish before the expected time. */ try { if(Properties.CTG_DEBUG_PORT != null){ //do not use timeout if we are debugging latch.await(); } else { //add one extra minute just to be sure boolean elapsed = !latch.await(configuration.timeInMinutes + 1, TimeUnit.MINUTES); if (elapsed) { logger.error("The jobs did not finish in time"); } } } catch (InterruptedException e) { } } public String getProjectClassPath() { return projectClassPath; } public StorageManager getStorage() { return storage; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy