/** * OpenSpotLight - Open Source IT Governance Platform * * Copyright (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA * or third-party contributors as indicated by the @author tags or express * copyright attribution statements applied by the authors. All third-party * contributions are distributed under license by CARAVELATECH CONSULTORIA E * TECNOLOGIA EM INFORMATICA LTDA. * * This copyrighted material is made available to anyone wishing to use, modify, * copy, or redistribute it subject to the terms and conditions of the GNU * Lesser General Public License, as published by the Free Software Foundation. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this distribution; if not, write to: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA * *********************************************************************** * OpenSpotLight - Plataforma de Governança de TI de Código Aberto * * Direitos Autorais Reservados (c) 2009, CARAVELATECH CONSULTORIA E TECNOLOGIA * EM INFORMATICA LTDA ou como contribuidores terceiros indicados pela etiqueta * @author ou por expressa atribuição de direito autoral declarada e atribuída pelo autor. * Todas as contribuições de terceiros estão distribuídas sob licença da * CARAVELATECH CONSULTORIA E TECNOLOGIA EM INFORMATICA LTDA. * * Este programa é software livre; você pode redistribuí-lo e/ou modificá-lo sob os * termos da Licença Pública Geral Menor do GNU conforme publicada pela Free Software * Foundation. * * Este programa é distribuído na expectativa de que seja útil, porém, SEM NENHUMA * GARANTIA; nem mesmo a garantia implícita de COMERCIABILIDADE OU ADEQUAÇÃO A UMA * FINALIDADE ESPECÍFICA. Consulte a Licença Pública Geral Menor do GNU para mais detalhes. * * Você deve ter recebido uma cópia da Licença Pública Geral Menor do GNU junto com este * programa; se não, escreva para: * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ package org.openspotlight.common.taskexec; import static org.openspotlight.common.util.Assertions.checkCondition; import static org.openspotlight.common.util.Assertions.checkNotEmpty; import static org.openspotlight.common.util.Assertions.checkNotNull; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.PriorityBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; import org.openspotlight.common.concurrent.GossipExecutor; import org.openspotlight.common.concurrent.Priority; import org.openspotlight.common.task.exception.PoolAlreadyStoppedException; import org.openspotlight.common.task.exception.RunnableWithException; import org.openspotlight.common.task.exception.RunningPriorityBigger; import org.openspotlight.common.task.exception.TaskAlreadyOnPoolException; import org.openspotlight.common.task.exception.TaskAlreadyRunnedException; import org.openspotlight.common.task.exception.TaskRunningException; import org.openspotlight.common.util.Exceptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public enum TaskExecManager { INSTANCE; private static class TaskBuilderImpl implements TaskExecBuilder { private String description = null; private final CopyOnWriteArrayList<TaskExec> parents = new CopyOnWriteArrayList<TaskExec>(); private final AtomicBoolean published = new AtomicBoolean(false); private final AtomicBoolean started = new AtomicBoolean(false); private final TaskGroupImpl taskGroup; private String taskId; private RunnableWithException thisRunnable; public TaskBuilderImpl( final TaskGroupImpl taskGroup) { this.taskGroup = taskGroup; } @Override public TaskExec andPublishTask() { checkCondition("notPublished", published.get() == false); final TaskImpl task = new TaskImpl(parents, description, thisRunnable, taskId); taskGroup.addTaskToPool(task); published.set(true); return task; } @Override public TaskExecBuilder withParentTasks(final Iterable<TaskExec> parent) { checkCondition("notStarted", started.get() == false); for (final TaskExec t: parent) { parents.add(t); } return this; } @Override public TaskExecBuilder withParentTasks(final TaskExec... parent) { checkCondition("notStarted", started.get() == false); for (final TaskExec t: parent) { parents.add(t); } return this; } @Override public TaskExecBuilder withReadableDescription(final String readableDescription) { checkNotEmpty("readableDescription", readableDescription); checkCondition("notPublished", published.get() == false); checkCondition("notStarded", started.get() == false); checkCondition("withoutDescription", description == null); description = readableDescription; return this; } @Override public TaskExecBuilder withReadableDescriptionAndUniqueId(final String readableDescriptionAndUniqueId) { checkNotEmpty("readableDescriptionAndUniqueId", readableDescriptionAndUniqueId); withUniqueId(readableDescriptionAndUniqueId); withReadableDescription(readableDescriptionAndUniqueId); return this; } @Override public TaskExecBuilder withRunnable(final RunnableWithException runnable) { checkNotNull("runnable", runnable); checkCondition("notPublished", published.get() == false); checkCondition("notStarded", started.get() == false); checkCondition("withoutRunnable", thisRunnable == null); thisRunnable = runnable; return this; } @Override public TaskExecBuilder withUniqueId(final String uniqueId) { checkNotEmpty("uniqueId", uniqueId); checkCondition("notPublished", published.get() == false); checkCondition("notStarded", started.get() == false); checkCondition("withoutUniqueId:" + taskId, taskId == null); taskId = uniqueId; return this; } } private static class TaskGroupImpl implements TaskExecGroup { private final List<String> alreadyRunnedTaskIds; private final AtomicReference<Priority> currentPriorityRunning; private final ReentrantLock lock; private final Logger logger = LoggerFactory.getLogger(getClass()); private final String name; private final String poolName; private final BlockingQueue<TaskImpl> queue; private final List<String> runningTaskIds; private final CountDownLatch stopped; private final Priority thisGroupPriority; LinkedBlockingQueue<TaskImpl> tasksForThisPriority; public TaskGroupImpl( final Priority thisGroupPriority, final CountDownLatch stopped, final BlockingQueue<TaskImpl> queue, final List<String> alreadyRunnedTaskIds, final List<String> runningTaskIds, final ReentrantLock lock, final AtomicReference<Priority> currentPriorityRunning, final LinkedBlockingQueue<TaskImpl> tasksForThisPriority, final String name, final String poolName) { this.thisGroupPriority = thisGroupPriority; this.stopped = stopped; this.queue = queue; this.alreadyRunnedTaskIds = alreadyRunnedTaskIds; this.runningTaskIds = runningTaskIds; this.lock = lock; this.currentPriorityRunning = currentPriorityRunning; this.tasksForThisPriority = tasksForThisPriority; this.name = name; this.poolName = poolName; } public void addTaskToPool(final TaskExec task) throws TaskAlreadyOnPoolException, TaskAlreadyRunnedException, TaskRunningException, PoolAlreadyStoppedException, RunningPriorityBigger { try { lock.lock(); final Priority curPriority = currentPriorityRunning.get(); if (stopped.getCount() == 0) { Exceptions.logAndThrow(new PoolAlreadyStoppedException()); } if (alreadyRunnedTaskIds.contains(task.getUniqueId())) { Exceptions.logAndThrow(new TaskAlreadyRunnedException("task already runned: " + task.getUniqueId())); } if (runningTaskIds.contains(task.getUniqueId())) { Exceptions.logAndThrow(new TaskRunningException("task running: " + task.getUniqueId())); } if (curPriority != null && curPriority.compareTo(thisGroupPriority) > 0) { Exceptions.logAndThrow(new RunningPriorityBigger()); } if (curPriority != null && curPriority.compareTo(thisGroupPriority) == 0) { if (queue.contains(task)) { Exceptions.logAndThrow(new TaskAlreadyOnPoolException()); } queue.add((TaskImpl) task); if (logger.isDebugEnabled()) { logger.debug("added task " + task.getUniqueId() + " " + task.getReadableDescription() + " to the current pool " + poolName + " and group " + name); } } else { if (tasksForThisPriority.contains(task)) { Exceptions.logAndThrow(new TaskAlreadyOnPoolException()); } tasksForThisPriority.add((TaskImpl) task); if (logger.isDebugEnabled()) { logger.debug("added task " + task.getUniqueId() + " " + task.getReadableDescription() + " to the future pool " + poolName + " and group " + name); } } } finally { lock.unlock(); } } @Override public String getName() { return name; } @Override public Priority getPriority() { return thisGroupPriority; } @Override public TaskExecBuilder prepareTask() { return new TaskBuilderImpl(this); } } private static class TaskImpl implements TaskExec { private final CountDownLatch latch = new CountDownLatch(1); private final Logger logger = LoggerFactory.getLogger(getClass()); private final List<TaskExec> parentTasks; private final String readableDescription; private final RunnableWithException runnable; private final String uniqueId; public TaskImpl( final List<TaskExec> parentTasks, final String readableDescription, final RunnableWithException runnable, final String uniqueId) { this.parentTasks = parentTasks; this.readableDescription = readableDescription; this.runnable = runnable; this.uniqueId = uniqueId; } @Override public void awaitToRun() throws InterruptedException { if (logger.isDebugEnabled()) { logger.debug("verifying if parents did run for task " + getUniqueId() + " " + getReadableDescription()); } for (final TaskExec parent: parentTasks) { if (logger.isDebugEnabled()) { logger.debug("verifying if parent " + parent.getUniqueId() + " " + parent.getReadableDescription() + " did run for task " + getUniqueId() + " " + getReadableDescription()); } parent.awaitToRunChild(); if (logger.isDebugEnabled()) { logger.debug("parent " + parent.getUniqueId() + " " + parent.getReadableDescription() + " runned for task " + getUniqueId() + " " + getReadableDescription()); } } if (logger.isDebugEnabled()) { logger.debug("all parents runned for task " + getUniqueId() + " " + getReadableDescription()); } } @Override public void awaitToRunChild() throws InterruptedException { latch.await(); } @Override public boolean didRun() { return latch.getCount() == 0l; } @Override public List<TaskExec> getParentTasks() { return parentTasks; } @Override public String getReadableDescription() { return readableDescription; } @Override public RunnableWithException getRunnable() { return runnable; } @Override public String getUniqueId() { return uniqueId; } @Override public void run() throws Exception { if (didRun()) { Exceptions.logAndThrow(new IllegalArgumentException("trying to run a task more than once: " + getReadableDescription())); } try { runnable.run(); } finally { latch.countDown(); } } } private static class TaskPoolImpl implements TaskExecPool { private final List<String> alreadyRunnedTaskIds = new CopyOnWriteArrayList<String>(); private final AtomicReference<Priority> currentPriorityRunning = new AtomicReference<Priority>(); private final GossipExecutor executor; private final BlockingQueue<Priority> existentPriorities = new PriorityBlockingQueue<Priority>(); private final CopyOnWriteArrayList<RunnableListener> listeners = new CopyOnWriteArrayList<RunnableListener>(); private final ReentrantLock lock = new ReentrantLock(true); private final String poolName; private final int poolSize; private final BlockingQueue<TaskImpl> queue = new LinkedBlockingQueue<TaskImpl>(); private final List<String> runningTaskIds = new CopyOnWriteArrayList<String>(); private final CountDownLatch stopped = new CountDownLatch(1); private final Map<Priority, BlockingQueue<TaskImpl>> taskMap = new ConcurrentHashMap<Priority, BlockingQueue<TaskImpl>>(); public TaskPoolImpl( final String poolName, final int poolSize) { this.poolName = poolName; this.poolSize = poolSize; executor = GossipExecutor.newFixedThreadPool(poolSize, poolName); } @Override public void addListener(final RunnableListener listener) { listeners.add(listener); } @Override public TaskExecGroup createTaskGroup(final String taskGroupName, final int... priorities) throws RunningPriorityBigger { try { lock.lock(); final Priority priorityAsObj = Priority.createPriority(priorities); final Priority currentPriority = currentPriorityRunning.get(); if (currentPriority != null) { if (currentPriority.compareTo(priorityAsObj) < 0) { Exceptions.logAndThrow(new RunningPriorityBigger()); } } existentPriorities.add(priorityAsObj); final LinkedBlockingQueue<TaskImpl> tasksForThisPriority = new LinkedBlockingQueue<TaskImpl>(); taskMap.put(priorityAsObj, tasksForThisPriority); return new TaskGroupImpl(priorityAsObj, stopped, queue, alreadyRunnedTaskIds, runningTaskIds, lock, currentPriorityRunning, tasksForThisPriority, taskGroupName, poolName); } finally { lock.unlock(); } } @Override public String getPoolName() { return poolName; } @Override public boolean isRunningAnyTask() { return runningTaskIds.size() > 0; } @Override public void removeListener(final RunnableListener listener) { listeners.remove(listener); } @Override public void startExecutorBlockingUntilFinish() throws InterruptedException { startExecutorOnBackground(); stopped.await(); } @Override public void startExecutorOnBackground() { for (int i = 0; i < poolSize; i++) { executor.execute(new Worker(listeners, poolName + "_" + i, stopped, queue, alreadyRunnedTaskIds, runningTaskIds, lock, currentPriorityRunning, existentPriorities, taskMap)); } executor.shutdown(); } } private static class Worker implements Runnable { private final List<String> alreadyRunnedTaskIds; private final AtomicReference<Priority> currentPriorityRunning; private final AtomicReference<TaskImpl> currentTask = new AtomicReference<TaskImpl>(); private final BlockingQueue<Priority> existentPriorities; private final CopyOnWriteArrayList<RunnableListener> listeners; private final ReentrantLock lock; private final Logger logger = LoggerFactory.getLogger(getClass()); private final BlockingQueue<TaskImpl> queue; private final List<String> runningTaskIds; private final CountDownLatch stopped; private final Map<Priority, BlockingQueue<TaskImpl>> taskMap; private final Map<String, Object> threadLocalMap = new HashMap<String, Object>(); private final String workerId; public Worker( final CopyOnWriteArrayList<RunnableListener> listeners, final String workerId, final CountDownLatch stopped, final BlockingQueue<TaskImpl> queue, final List<String> alreadyRunnedTaskIds, final List<String> runningTaskIds, final ReentrantLock lock, final AtomicReference<Priority> currentPriorityRunning, final BlockingQueue<Priority> existentPriorities, final Map<Priority, BlockingQueue<TaskImpl>> taskMap) { this.stopped = stopped; this.queue = queue; this.alreadyRunnedTaskIds = alreadyRunnedTaskIds; this.runningTaskIds = runningTaskIds; this.lock = lock; this.workerId = workerId; this.listeners = listeners; this.currentPriorityRunning = currentPriorityRunning; this.existentPriorities = existentPriorities; this.taskMap = taskMap; } @Override public void run() { try { for (final RunnableListener l: listeners) { l.beforeSetupWorker(threadLocalMap); } } catch (final Exception e) { Exceptions.catchAndLog(e); } while (stopped.getCount() != 0) { TaskImpl task = null; try { if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": locking to get a task"); } lock.lock(); if (queue.size() > 0) { task = queue.take(); } else { if (runningTaskIds.size() == 0) { if (existentPriorities.size() > 0) { final Priority priority = existentPriorities.poll(); currentPriorityRunning.set(priority); final BlockingQueue<TaskImpl> tasksForThisPriority = taskMap.get(priority); taskMap.remove(priority); queue.addAll(tasksForThisPriority); logger.info("worker " + workerId + ": needs to get new tasks for another priority: from " + currentPriorityRunning + " to " + priority); continue; } else { currentPriorityRunning.set(null); logger.info("worker " + workerId + ": no more priorities. Going to shutdown"); stopped.countDown(); } } } if (task != null) { runningTaskIds.add(task.getUniqueId()); if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": getting task " + task.getUniqueId() + " " + task.getReadableDescription()); } if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": unlocking to get a task"); } } } catch (final Exception e) { Exceptions.catchAndLog(e); } finally { lock.unlock(); } if (task == null) { continue; } try { if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": waiting to run task " + task.getUniqueId() + " " + task.getReadableDescription()); } currentTask.set(task); try { for (final RunnableListener l: listeners) { l.beforeRunningTask(threadLocalMap, task.getRunnable()); } } catch (final Exception e) { Exceptions.catchAndLog(e); } task.awaitToRun(); if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": about to run task " + task.getUniqueId() + " " + task.getReadableDescription()); } task.run(); try { for (final RunnableListener l: listeners) { l.afterRunningTask(threadLocalMap, task.getRunnable()); } } catch (final Exception e) { Exceptions.catchAndLog(e); } currentTask.set(null); } catch (final Exception e) { if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": error on task " + task.getUniqueId() + " " + task.getReadableDescription()); } Exceptions.catchAndLog(e); } try { if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": locking to update task information "); } lock.lock(); alreadyRunnedTaskIds.add(task.getUniqueId()); runningTaskIds.remove(task.getUniqueId()); try { if (!(queue.size() == 0 && runningTaskIds.size() == 0 && taskMap.size() == 0)) { continue; } } catch (final Exception e) { Exceptions.catchAndLog(e); } } catch (final Exception e) { Exceptions.catchAndLog(e); } finally { lock.unlock(); if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": finished to run task " + task.getUniqueId() + " " + task.getReadableDescription()); } if (logger.isDebugEnabled()) { logger.debug("worker " + workerId + ": unlocking to update task information "); } } } if (logger.isDebugEnabled()) { logger.debug("stopping worker " + workerId); } try { for (final RunnableListener l: listeners) { l.beforeShutdownWorker(threadLocalMap); } } catch (final Exception e) { Exceptions.catchAndLog(e); } } } public TaskExecPool createTaskPool(final String poolName, final int poolSize) { checkNotEmpty("poolName", poolName); return new TaskPoolImpl(poolName, poolSize); } }