package com.plectix.simulator.controller;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import com.plectix.simulator.streaming.LiveData;
/**
* This class runs jobs in a thread pool. The thread pool is created only once, when any one
* of the constructors are called and then its size can not be changed again. The default
* size is the number of available processors on the system. One can submit simulation
* jobs to this class, and monitor their progress either through using call-back functions,
* blocking calls to this class, or looping over non-blocking calls to this class.
*
* @author ecemis
*/
public class SimulationService {
private static final int DEFAULT_NUMBER_OF_THREADS = Runtime.getRuntime().availableProcessors();
private static ExecutorService EXECUTOR_SERVICE = null;
private SimulatorFactoryInterface simulatorFactory = null;
private ConcurrentHashMap<Long, SimulatorFutureTask> callablesMap = new ConcurrentHashMap<Long, SimulatorFutureTask>();
/**
* Creates a SimulationService which would use the given simulatorFactoryInterface
* to create new Simulators to work in parallel threads. The first call to any one
* of the constructors creates the thread pool whose size can not be changed.
*
* @param simulatorFactoryInterface
*/
public SimulationService(SimulatorFactoryInterface simulatorFactoryInterface) {
this(simulatorFactoryInterface, DEFAULT_NUMBER_OF_THREADS);
}
/**
* Creates a SimulationService which would use the given simulatorFactoryInterface
* to create new Simulators to work in parallel threads. The first call to any one
* of the constructors creates the thread pool whose size can not be changed.
*
* @param simulatorFactoryInterface
* @param numberOfThreads
*/
private SimulationService(SimulatorFactoryInterface simulatorFactoryInterface, int numberOfThreads) {
if (simulatorFactoryInterface == null) {
throw new RuntimeException("We need a simulator factory!");
}
this.simulatorFactory = simulatorFactoryInterface;
if (EXECUTOR_SERVICE == null) {
EXECUTOR_SERVICE = Executors.newFixedThreadPool(numberOfThreads);
}
}
/**
* Submits simulatorInputData to get executed by the thread pool and returns an ID to query the
* job progress. The progress can also be monitored through the callback functions of the
* listener passed to this method.
*
* @param simulatorInputData
* @param listener
* @return the job ID to query the progress
*/
public long submit(SimulatorInputData simulatorInputData, SimulatorCallableListener listener) {
SimulatorCallable simulatorCallable = new SimulatorCallable(simulatorFactory.createSimulator(), simulatorInputData, listener);
SimulatorFutureTask futureTask = new SimulatorFutureTask(simulatorCallable);
// submit task:
EXECUTOR_SERVICE.submit(futureTask);
// cash the ID number
callablesMap.put(simulatorCallable.getId(), futureTask);
return simulatorCallable.getId();
}
/**
* Submits a collection of jobs to run.
*
* @param simulationInputDataList
* @param listener
* @return a {@link SimulatorProgressMonitor}
*/
public SimulatorProgressMonitor submit(List<SimulatorInputData> simulationInputDataList, SimulatorCallableListener listener) {
SimulatorProgressMonitor progressMonitor = new SimulatorProgressMonitor(
simulatorFactory, simulationInputDataList, listener,
new ExecutorCompletionService<SimulatorResultsData>(EXECUTOR_SERVICE));
return progressMonitor;
}
/**
* Waits if necessary for the computation of jobID to complete, and then retrieves its result.
* Returns null if there is no job with id jobID. Note that the jobID is removed from the queue
* no matter what this method returns. I.e. One can not call this function twice with the same jobID.
*
* @param jobID
* @param timeout
* @param unit
* @return the result of the simulation as {@link SimulatorResultsData}
*/
public SimulatorResultsData getSimulatorResultsData(long jobID, long timeout, TimeUnit unit) {
SimulatorFutureTask futureTask = callablesMap.get(jobID);
if (futureTask == null) {
return null;
}
try {
return futureTask.get(timeout, unit);
} catch (Exception e) {
SimulatorResultsData simulatorResultsData = futureTask.getSimulator().getSimulatorResultsData();
simulatorResultsData.getSimulatorExitReport().setException(e);
return simulatorResultsData;
} finally {
callablesMap.remove(jobID);
}
}
/**
* Returns the current simulation status for <code>jobID</code> or
* <code>null</code> if the job doesn't exist.
*
* @param jobID
* @return the current status
*/
public SimulatorStatusInterface getSimulatorStatus(long jobID) {
SimulatorFutureTask futureTask = callablesMap.get(jobID);
if (futureTask == null) {
return null;
}
return futureTask.getSimulator().getStatus();
}
/**
* Returns the streaming live data for <code>jobID</code> or
* <code>null</code> if the job doesn't exist.
*
* @param jobID
* @return the current status
*/
public LiveData getSimulatorLiveData(long jobID) {
SimulatorFutureTask futureTask = callablesMap.get(jobID);
if (futureTask == null) {
return null;
}
return futureTask.getSimulator().getLiveData();
}
/**
* Attempts to cancel execution of this job. This attempt will fail
* if the task has already completed, has already been canceled, or
* could not be canceled for some other reason. <br>
* If successful, and this job has not started when cancel is called,
* this job should never run. <br>
* If the job has already started, then the <code>mayInterruptIfRunning</code>
* parameter determines whether the thread executing this job should be interrupted
* in an attempt to stop the job.<br>
* <br>
* After this method returns, subsequent calls to <code>isDone()</code> will
* always return true. Subsequent calls to <code>isCancelled()</code>
* will always return <code>true</code> if this method returned true.
*
* @param jobID
* @param mayInterruptIfRunning - <code>true</code> if the thread executing this job
* should be interrupted; otherwise, in-progress jobs are allowed to complete
* @param removeJob - if <code>true</code> the jobID can not be queried again.
*
* @return <code>false</code> if the jobId doesn't exist, the job could not
* be canceled, typically because it has already completed normally;
* <code>true</code> otherwise.
*/
public boolean cancel(long jobID, boolean mayInterruptIfRunning, boolean removeJob) {
SimulatorFutureTask futureTask = callablesMap.get(jobID);
if (futureTask == null) {
return false;
}
boolean ret = futureTask.cancel(mayInterruptIfRunning);
if (removeJob) {
callablesMap.remove(jobID);
}
return ret;
}
/**
* Returns true if this job doesn't exist or was canceled before it completed normally.
*
* @param jobID
* @return true if this job doesn't exist or was canceled before it completed normally.
*/
public boolean isCancelled(long jobID) {
SimulatorFutureTask futureTask = callablesMap.get(jobID);
if (futureTask == null) {
return true;
}
return futureTask.isCancelled();
}
/**
* Returns true if this task completed. Completion may be due to normal termination,
* an exception, or cancellation -- in all of these cases, this method will return true.
*
* @param jobID
* @return true if this task completed.
*/
public boolean isDone(long jobID) {
SimulatorFutureTask futureTask = callablesMap.get(jobID);
if (futureTask == null) {
return false;
}
return futureTask.isDone();
}
/**
* Initiates an orderly shutdown in which previously submitted
* tasks are executed, but no new tasks will be
* accepted.
* Invocation has no additional effect if already shut down.
*
* @throws SecurityException if a security manager exists and
* shutting down this ExecutorService may manipulate threads that
* the caller is not permitted to modify because it does not hold
* {@link java.lang.RuntimePermission}<tt>("modifyThread")</tt>,
* or the security manager's <tt>checkAccess</tt> method denies access.
*/
public void shutdown() {
EXECUTOR_SERVICE.shutdown();
}
}