package i5.las2peer.services.ocd.utils; import i5.las2peer.services.ocd.algorithms.OcdAlgorithm; import i5.las2peer.services.ocd.benchmarks.GroundTruthBenchmark; import i5.las2peer.services.ocd.graphs.Cover; import i5.las2peer.services.ocd.graphs.CoverCreationLog; import i5.las2peer.services.ocd.graphs.CoverId; import i5.las2peer.services.ocd.graphs.CustomGraph; import i5.las2peer.services.ocd.graphs.CustomGraphId; import i5.las2peer.services.ocd.graphs.GraphCreationLog; import i5.las2peer.services.ocd.metrics.KnowledgeDrivenMeasure; import i5.las2peer.services.ocd.metrics.OcdMetricLog; import i5.las2peer.services.ocd.metrics.OcdMetricLogId; import i5.las2peer.services.ocd.metrics.StatisticalMeasure; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.logging.Level; import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; /** * Handles the execution and synchronization of threads for running algorithms, benchmarks and metrics. * @author Sebastian * */ public class ThreadHandler { /** * Defines how many threads are allowed to run in parallel. */ private static final int THREAD_POOL_SIZE = 4; /** * Mapping from the id of a cover in calculation to the future of the algorithm calculating it. */ private static Map<CoverId, Future<CoverCreationLog>> algorithms = new HashMap<CoverId, Future<CoverCreationLog>>(); /** * Mapping from the id of a metric being calculated to the future of its execution. */ private static Map<OcdMetricLogId, Future<OcdMetricLog>> metrics = new HashMap<OcdMetricLogId, Future<OcdMetricLog>>(); /** * Mapping from the id of a graph in calculation to the future of the benchmark calculating it. */ private static Map<CustomGraphId, Future<GraphCreationLog>> benchmarks = new HashMap<CustomGraphId, Future<GraphCreationLog>>(); /** * A thread pool for the execution of algorithm, benchmark and metric runnables. */ private static ExecutorService executor = Executors.newFixedThreadPool(THREAD_POOL_SIZE); /** * Creates a new instance. */ private RequestHandler requestHandler = new RequestHandler(); /** * Runs an algorithm. * @param cover The cover that is already persisted but not holding any valid information aside the graph and id. * @param algorithm The algorithm to calculate the cover with. * @param componentNodeCountFilter The node count filter used by the OcdAlgorithmExecutor. */ public void runAlgorithm(Cover cover, OcdAlgorithm algorithm, int componentNodeCountFilter) { CustomGraphId gId = new CustomGraphId(cover.getGraph().getId(), cover.getGraph().getUserName()); CoverId coverId = new CoverId(cover.getId(), gId); AlgorithmRunnable runnable = new AlgorithmRunnable(cover, algorithm, componentNodeCountFilter, this); CoverCreationLog log = cover.getCreationMethod(); synchronized (algorithms) { Future<CoverCreationLog> future = executor.<CoverCreationLog>submit(runnable, log); algorithms.put(coverId, future); } } /** * Runs a ground truth benchmark. * @param cover The id for the reserved, prepersisted ground truth cover. * @param benchmark The benchmark model to calculate the ground truth cover with. */ public void runGroundTruthBenchmark(Cover cover, GroundTruthBenchmark benchmark) { CustomGraphId gId = new CustomGraphId(cover.getGraph().getId(), cover.getGraph().getUserName()); CoverId coverId = new CoverId(cover.getId(), gId); GroundTruthBenchmarkRunnable runnable = new GroundTruthBenchmarkRunnable(coverId, benchmark, this); GraphCreationLog log = cover.getGraph().getCreationMethod(); synchronized (benchmarks) { Future<GraphCreationLog> future = executor.<GraphCreationLog>submit(runnable, log); benchmarks.put(gId, future); } } /** * Runs a statistical measure. * @param metricLog The reserved prepersisted log entry of the metric. * @param metric The metric to run. * @param cover The cover the metric shall run on. */ public void runStatisticalMeasure(OcdMetricLog metricLog, StatisticalMeasure metric, Cover cover) { CustomGraphId gId = new CustomGraphId(cover.getGraph().getId(), cover.getGraph().getUserName()); CoverId coverId = new CoverId(cover.getId(), gId); OcdMetricLogId logId = new OcdMetricLogId(metricLog.getId(), coverId); StatisticalMeasureRunnable runnable = new StatisticalMeasureRunnable(logId, metric, cover, this); synchronized (metrics) { Future<OcdMetricLog> future = executor.<OcdMetricLog>submit(runnable, metricLog); metrics.put(logId, future); } } /** * Runs a knowledge-driven measure. * @param metricLog The reserved prepersisted log entry of the metric. * @param metric The metric to run. * @param cover The cover the metric shall run on. * @param groundTruth The ground truth cover to be used by the metric. */ public void runKnowledgeDrivenMeasure(OcdMetricLog metricLog, KnowledgeDrivenMeasure metric, Cover cover, Cover groundTruth) { CustomGraphId gId = new CustomGraphId(cover.getGraph().getId(), cover.getGraph().getUserName()); CoverId coverId = new CoverId(cover.getId(), gId); OcdMetricLogId logId = new OcdMetricLogId(metricLog.getId(), coverId); KnowledgeDrivenMeasureRunnable runnable = new KnowledgeDrivenMeasureRunnable(logId, metric, cover, groundTruth, this); synchronized (metrics) { Future<OcdMetricLog> future = executor.<OcdMetricLog>submit(runnable, metricLog); metrics.put(logId, future); } } /** * Merges a calculated metric to the persistence context. * Is called from the runnable itself. * @param log The calculated metric. * May be null if error is true. * @param logId The id reserved for the calculated metric. * @param error States whether an error occurred (true) during execution. */ public void createMetric(OcdMetricLog log, OcdMetricLogId logId, boolean error) { synchronized (metrics) { if(Thread.interrupted()) { Thread.currentThread().interrupt(); return; } if(!error) { EntityManager em = requestHandler.getEntityManager(); EntityTransaction tx = em.getTransaction(); try { tx.begin(); OcdMetricLog persistedLog = em.find(OcdMetricLog.class, logId); if(persistedLog == null) { /* * Should not happen. */ requestHandler.log(Level.SEVERE, "Log deleted while metric running."); throw new IllegalStateException(); } persistedLog.setValue(log.getValue()); persistedLog.setStatus(ExecutionStatus.COMPLETED); tx.commit(); } catch( RuntimeException e ) { if( tx != null && tx.isActive() ) { tx.rollback(); } error = true; } em.close(); } if(error) { EntityManager em = requestHandler.getEntityManager(); EntityTransaction tx = em.getTransaction(); try { tx.begin(); OcdMetricLog persistedLog = em.find(OcdMetricLog.class, logId); if(persistedLog == null) { /* * Should not happen. */ requestHandler.log(Level.SEVERE, "Log deleted while metric running."); throw new IllegalStateException(); } persistedLog.setStatus(ExecutionStatus.ERROR); tx.commit(); } catch( RuntimeException e ) { if( tx != null && tx.isActive() ) { tx.rollback(); } } em.close(); } unsynchedInterruptMetric(logId); } } /** * Merges a calculated ground truth cover created by a ground truth benchmark to the persistence context. * Is called from the runnable itself. * @param calculatedCover The calculated ground truth cover holding also the corresponding calculated graph. * May be null if error is true. * @param coverId The id reserved for the ground truth cover. * @param error Indicates whether an error occurred (true) during the calculation. */ public void createGroundTruthCover(Cover calculatedCover, CoverId coverId, boolean error) { synchronized (benchmarks) { if(Thread.interrupted()) { Thread.currentThread().interrupt(); return; } if(!error) { EntityManager em = requestHandler.getEntityManager(); EntityTransaction tx = em.getTransaction(); try { tx.begin(); Cover cover = em.find(Cover.class, coverId); if(cover == null) { /* * Should not happen. */ requestHandler.log(Level.WARNING, "Cover deleted while benchmark running."); throw new IllegalStateException(); } cover.getGraph().setStructureFrom(calculatedCover.getGraph()); cover.getGraph().getCreationMethod().setStatus(ExecutionStatus.COMPLETED); tx.commit(); } catch( RuntimeException ex ) { if( tx != null && tx.isActive() ) tx.rollback(); error = true; } em.close(); em = requestHandler.getEntityManager(); tx = em.getTransaction(); try { tx.begin(); Cover cover = em.find(Cover.class, coverId); if(cover == null) { /* * Should not happen. */ requestHandler.log(Level.WARNING, "Cover deleted while benchmark running."); throw new IllegalStateException(); } cover.setMemberships(calculatedCover.getMemberships()); cover.getCreationMethod().setStatus(ExecutionStatus.COMPLETED); tx.commit(); } catch( RuntimeException ex ) { if( tx != null && tx.isActive() ) tx.rollback(); error = true; } em.close(); } if(error) { EntityManager em = requestHandler.getEntityManager(); EntityTransaction tx = em.getTransaction(); try { tx.begin(); Cover cover = em.find(Cover.class, coverId); if(cover == null) { /* * Should not happen. */ requestHandler.log(Level.SEVERE, "Cover deleted while benchmark running."); throw new IllegalStateException(); } CustomGraph graph = cover.getGraph(); cover.getCreationMethod().setStatus(ExecutionStatus.ERROR); graph.getCreationMethod().setStatus(ExecutionStatus.ERROR); tx.commit(); } catch( RuntimeException e ) { if( tx != null && tx.isActive() ) { tx.rollback(); } } em.close(); } CustomGraphId graphId = coverId.getGraphId(); unsynchedInterruptBenchmark(graphId); } } /** * Merges a calculated cover to the persistence context. * Is called from the runnable itself. * @param calculatedCover The calculated cover. * May be null if error is true. * @param coverId The id reserved for the calculated cover. * @param error States whether an error occurred (true) during execution. */ public void createCover(Cover calculatedCover, CoverId coverId, boolean error) { synchronized (algorithms) { if(Thread.interrupted()) { Thread.currentThread().interrupt(); return; } if(!error) { EntityManager em = requestHandler.getEntityManager(); EntityTransaction tx = em.getTransaction(); try { tx.begin(); Cover cover = em.find(Cover.class, coverId); if(cover == null) { /* * Should not happen. */ requestHandler.log(Level.SEVERE, "Cover deleted while algorithm running."); throw new IllegalStateException(); } cover.setMemberships(calculatedCover.getMemberships()); OcdMetricLog calculatedExecTime = calculatedCover.getMetrics().get(0); OcdMetricLog log = new OcdMetricLog(calculatedExecTime.getType(), calculatedExecTime.getValue(), calculatedExecTime.getParameters(), cover); log.setStatus(ExecutionStatus.COMPLETED); cover.addMetric(log); cover.getCreationMethod().setStatus(ExecutionStatus.COMPLETED); tx.commit(); } catch( RuntimeException e ) { if( tx != null && tx.isActive() ) { tx.rollback(); } error = true; } em.close(); } if(error) { EntityManager em = requestHandler.getEntityManager(); EntityTransaction tx = em.getTransaction(); try { tx.begin(); Cover cover = em.find(Cover.class, coverId); if(cover == null) { /* * Should not happen. */ requestHandler.log(Level.SEVERE, "Cover deleted while algorithm running."); throw new IllegalStateException(); } cover.getCreationMethod().setStatus(ExecutionStatus.ERROR); tx.commit(); } catch( RuntimeException e ) { if( tx != null && tx.isActive() ) { tx.rollback(); } } em.close(); } unsynchedInterruptAlgorithm(coverId); } } /** * Interrupts the algorithm creating a cover. * @param coverId The id of the persisted cover reserved for the algorithm result. */ public void interruptAlgorithm(CoverId coverId) { synchronized (algorithms) { unsynchedInterruptAlgorithm(coverId); } } /** * Interrupts the benchmark creating a graph * (and in case of a ground truth benchmark also creating the corresponding cover). * @param graphId The id of the persisted graph reserved for the benchmark result. */ public void interruptBenchmark(CustomGraphId graphId) { synchronized (benchmarks) { unsynchedInterruptBenchmark(graphId); } } /** * Interrupts a metric running on a cover. * @param logId The id of the persisted log reserved for the metric result. */ public void interruptMetric(OcdMetricLogId logId) { synchronized (metrics) { unsynchedInterruptMetric(logId); } } /** * Interrupts the algorithm creating a cover or if already created * all metrics running it. * Note that ground truth benchmarks will not be interrupted. * @param cover The cover. */ public void interruptAll(Cover cover) { synchronized (algorithms) { unsynchedInterruptAlgorithm(new CoverId(cover.getId(), new CustomGraphId(cover.getGraph().getId(), cover.getGraph().getUserName()))); } synchronized (metrics) { unsynchedInterruptAllMetrics(cover); } } /** * Interrupts an algorithm execution without synchronization. * @param coverId The id of the reserved persisted cover being calculated by the algorithm. */ private void unsynchedInterruptAlgorithm(CoverId coverId) { Future<CoverCreationLog> future = algorithms.get(coverId); if(future != null) { future.cancel(true); algorithms.remove(future); } } /** * Interrupts a benchmark execution without synchronization. * @param graphId The id of the reserved persisted graph the benchmark is calculating. */ private void unsynchedInterruptBenchmark(CustomGraphId graphId) { Future<GraphCreationLog> future = benchmarks.get(graphId); if(future != null) { future.cancel(true); benchmarks.remove(future); } } /** * Interrupts a metric execution without synchronization. * @param cover The cover the metric is run on. * @param logId The id of the reserved persisted log the metric is calculating. */ private void unsynchedInterruptMetric(OcdMetricLogId logId) { Future<OcdMetricLog> future = metrics.get(logId); if(future != null) { future.cancel(true); metrics.remove(future); } } /** * Interrupts the execution of all metric running on a cover without synchronization. * @param cover The cover. */ private void unsynchedInterruptAllMetrics(Cover cover) { CoverId coverId = new CoverId(cover.getId(), new CustomGraphId(cover.getGraph().getId(), cover.getGraph().getUserName())); for(OcdMetricLog log : cover.getMetrics()) { OcdMetricLogId logId = new OcdMetricLogId(log.getId(), coverId); unsynchedInterruptMetric(logId); } } }