/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2009-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.core.tasks; import java.util.Map; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CompletionService; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorCompletionService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.opennms.core.concurrent.LogPreservingThreadFactory; import org.opennms.core.utils.ThreadCategory; import org.springframework.beans.factory.InitializingBean; import org.springframework.util.Assert; /** * TaskCoordinator * * @author brozow * @version $Id: $ */ public class DefaultTaskCoordinator implements InitializingBean { /** * A RunnableActor class is a thread that simple removes Future<Runnable> from a queue * and executes them. This * * @author brozow */ private class RunnableActor extends Thread { private final BlockingQueue<Future<Runnable>> m_queue; public RunnableActor(String name, BlockingQueue<Future<Runnable>> queue) { super(name); m_queue = queue; start(); } public void run() { while(true) { try { Runnable r = m_queue.take().get(); if (r != null) { r.run(); } if (m_loopDelay != null) { sleep(m_loopDelay); } } catch (InterruptedException e) { log().warn("runnable actor interrupted", e); Thread.currentThread().interrupt(); } catch (ExecutionException e) { log().warn("runnable actor execution failed", e); } catch (Throwable e) { log().error("an unknown error occurred in the runnable actor", e); } } } } private final BlockingQueue<Future<Runnable>> m_queue; private final ConcurrentHashMap<String, CompletionService<Runnable>> m_taskCompletionServices = new ConcurrentHashMap<String, CompletionService<Runnable>>(); @SuppressWarnings("unused") private final RunnableActor m_actor; private String m_defaultExecutor ; private CompletionService<Runnable> m_defaultCompletionService; // This is used to adjust timing during testing private Long m_loopDelay; /** * <p>Constructor for DefaultTaskCoordinator.</p> * * @param name a {@link java.lang.String} object. */ public DefaultTaskCoordinator(String name) { m_queue = new LinkedBlockingQueue<Future<Runnable>>(); m_actor = new RunnableActor(name+"-TaskScheduler", m_queue); addExecutor(SyncTask.ADMIN_EXECUTOR, Executors.newSingleThreadExecutor( new LogPreservingThreadFactory(SyncTask.ADMIN_EXECUTOR, 1, false) )); } /** * <p>Constructor for DefaultTaskCoordinator.</p> * * @param name a {@link java.lang.String} object. * @param defaultExecutor a {@link java.util.concurrent.Executor} object. */ public DefaultTaskCoordinator(String name, Executor defaultExecutor) { this(name); m_defaultExecutor = SyncTask.DEFAULT_EXECUTOR; addExecutor(SyncTask.DEFAULT_EXECUTOR, defaultExecutor); afterPropertiesSet(); } /** * <p>setDefaultExecutor</p> * * @param executorName a {@link java.lang.String} object. */ public void setDefaultExecutor(String executorName) { m_defaultExecutor = executorName; } /** * <p>afterPropertiesSet</p> */ @Override public void afterPropertiesSet() { Assert.notNull(m_defaultExecutor, "defaultExecutor must be set"); m_defaultCompletionService = getCompletionService(m_defaultExecutor); Assert.notNull(m_defaultCompletionService, "defaultExecutor must be set to the name of an added executor"); } /** * <p>createTask</p> * * @param parent a {@link org.opennms.core.tasks.ContainerTask} object. * @param r a {@link java.lang.Runnable} object. * @return a {@link org.opennms.core.tasks.SyncTask} object. */ public SyncTask createTask(ContainerTask<?> parent, Runnable r) { return new SyncTask(this, parent, r); } /** * <p>createTask</p> * * @param parent a {@link org.opennms.core.tasks.ContainerTask} object. * @param r a {@link java.lang.Runnable} object. * @param schedulingHint a {@link java.lang.String} object. * @return a {@link org.opennms.core.tasks.SyncTask} object. */ public SyncTask createTask(ContainerTask<?> parent, Runnable r, String schedulingHint) { return new SyncTask(this, parent, r, schedulingHint); } /** * <p>createTask</p> * * @param parent a {@link org.opennms.core.tasks.ContainerTask} object. * @param async a {@link org.opennms.core.tasks.Async} object. * @param cb a {@link org.opennms.core.tasks.Callback} object. * @param <T> a T object. * @return a {@link org.opennms.core.tasks.AsyncTask} object. */ public <T> AsyncTask<T> createTask(ContainerTask<?> parent, Async<T> async, Callback<T> cb) { return new AsyncTask<T>(this, parent, async, cb); } /** * <p>createBatch</p> * * @param parent a {@link org.opennms.core.tasks.ContainerTask} object. * @return a {@link org.opennms.core.tasks.TaskBuilder} object. */ public TaskBuilder<BatchTask> createBatch(ContainerTask<?> parent) { return new TaskBuilder<BatchTask>(new BatchTask(this, parent)); } /** * <p>createBatch</p> * * @return a {@link org.opennms.core.tasks.TaskBuilder} object. */ public TaskBuilder<BatchTask> createBatch() { return createBatch((ContainerTask<?>)null); } /** * <p>createBatch</p> * * @param parent a {@link org.opennms.core.tasks.ContainerTask} object. * @param tasks a {@link java.lang.Runnable} object. * @return a {@link org.opennms.core.tasks.BatchTask} object. */ public BatchTask createBatch(ContainerTask<?> parent, Runnable... tasks) { return createBatch(parent).add(tasks).get(parent); } /** * <p>createBatch</p> * * @param tasks a {@link java.lang.Runnable} object. * @return a {@link org.opennms.core.tasks.BatchTask} object. */ public BatchTask createBatch(Runnable... tasks) { return createBatch().add(tasks).get(); } /** * <p>createSequence</p> * * @param parent a {@link org.opennms.core.tasks.ContainerTask} object. * @return a {@link org.opennms.core.tasks.TaskBuilder} object. */ public TaskBuilder<SequenceTask> createSequence(ContainerTask<?> parent) { return new TaskBuilder<SequenceTask>(new SequenceTask(this, parent)); } /** * <p>createSequence</p> * * @return a {@link org.opennms.core.tasks.TaskBuilder} object. */ public TaskBuilder<SequenceTask> createSequence() { return createSequence((ContainerTask<?>)null); } /** * <p>createSequence</p> * * @param parent a {@link org.opennms.core.tasks.ContainerTask} object. * @param tasks a {@link java.lang.Runnable} object. * @return a {@link org.opennms.core.tasks.SequenceTask} object. */ public SequenceTask createSequence(ContainerTask<?> parent, Runnable... tasks) { return createSequence(parent).add(tasks).get(parent); } /** * <p>createSquence</p> * * @param tasks a {@link java.lang.Runnable} object. * @return a {@link org.opennms.core.tasks.SequenceTask} object. */ public SequenceTask createSquence(Runnable... tasks) { return createSequence().add(tasks).get(); } /** * <p>setLoopDelay</p> * * @param millis a long. */ public void setLoopDelay(long millis) { m_loopDelay = millis; } /** * <p>schedule</p> * * @param task a {@link org.opennms.core.tasks.Task} object. */ public void schedule(final Task task) { onProcessorThread(scheduler(task)); } /** * <p>addDependency</p> * * @param prereq a {@link org.opennms.core.tasks.Task} object. * @param dependent a {@link org.opennms.core.tasks.Task} object. */ public void addDependency(Task prereq, Task dependent) { // this is only needed when add dependencies while running dependent.incrPendingPrereqCount(); onProcessorThread(dependencyAdder(prereq, dependent)); } private void onProcessorThread(final Runnable r) { Future<Runnable> now = new Future<Runnable>() { public boolean cancel(boolean mayInterruptIfRunning) { return false; } public Runnable get() { return r; } public Runnable get(long timeout, TimeUnit unit) { return get(); } public boolean isCancelled() { return false; } public boolean isDone() { return true; } public String toString() { return "Future<"+r+">"; } }; m_queue.add(now); } private Runnable scheduler(final Task task) { return new Runnable() { public void run() { task.scheduled(); task.submitIfReady(); } public String toString() { return String.format("schedule(%s)", task); } }; } Runnable taskCompleter(final Task task) { return new Runnable() { public void run() { notifyDependents(task); } public String toString() { return String.format("notifyDependents(%s)", task); } }; } private void notifyDependents(Task completed) { // log().debug(String.format("Task %s completed!", completed)); completed.onComplete(); final Set<Task> dependents = completed.getDependents(); for(Task dependent : dependents) { dependent.doCompletePrerequisite(completed); if (dependent.isReady()) { // log().debug(String.format("Task %s %s ready.", dependent, dependent.isReady() ? "is" : "is not")); } dependent.submitIfReady(); } // log().debug(String.format("CLEAN: removing dependents of %s", completed)); completed.clearDependents(); } /** * The returns a runnable that is run on the taskCoordinator thread.. This is * done to keep the Task data structures thread safe. */ private Runnable dependencyAdder(final Task prereq, final Task dependent) { Assert.notNull(prereq, "prereq must not be null"); Assert.notNull(dependent, "dependent must not be null"); return new Runnable() { public void run() { prereq.doAddDependent(dependent); dependent.doAddPrerequisite(prereq); dependent.decrPendingPrereqCount(); /** * the prereq task may have completed between the time this adder was enqueued * and the time we got here. In this case there will be no tasks to kick this * one off... so check it here. */ dependent.submitIfReady(); } public String toString() { return String.format("%s.addPrerequisite(%s)", dependent, prereq); } }; } private CompletionService<Runnable> getCompletionService(String name) { CompletionService<Runnable> completionService = m_taskCompletionServices.get(name); CompletionService<Runnable> selected = completionService != null ? completionService : m_defaultCompletionService; // log().debug(String.format("USING COMPLETION SERVICE %s : %s!", name, selected)); return selected; } void markTaskAsCompleted(Task task) { onProcessorThread(taskCompleter(task)); } void submitToExecutor(String executorPreference, Runnable workToBeDone, Task owningTask) { submitToExecutor(executorPreference, workToBeDone, taskCompleter(owningTask)); } void submitToExecutor(String executorPreference, final Runnable workToBeDone, Runnable completionProcessor) { getCompletionService(executorPreference).submit(workToBeDone, completionProcessor); } /** * <p>addExecutor</p> * * @param executorName a {@link java.lang.String} object. * @param executor a {@link java.util.concurrent.Executor} object. */ public void addExecutor(String executorName, Executor executor) { m_taskCompletionServices.put(executorName, new ExecutorCompletionService<Runnable>(executor, m_queue)); } /** * <p>setExecutors</p> * * @param executors a {@link java.util.Map} object. */ public void setExecutors(Map<String,Executor> executors) { m_taskCompletionServices.clear(); for (Map.Entry<String, Executor> e : executors.entrySet()) { addExecutor(e.getKey(), e.getValue()); } } private ThreadCategory log() { return ThreadCategory.getInstance(getClass()); } }