/******************************************************************************** * CruiseControl, a Continuous Integration Toolkit * Copyright (c) 2001, ThoughtWorks, Inc. * 200 E. Randolph, 25th Floor * Chicago, IL 60601 USA * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * + Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * + Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided * with the distribution. * * + Neither the name of ThoughtWorks, Inc., CruiseControl, nor the * names of its contributors may be used to endorse or promote * products derived from this software without specific prior * written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ********************************************************************************/ package net.sourceforge.cruisecontrol.util.threadpool; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; /** * Used to encapsulate the concept of a Thread Pool * <P> * The queue accepts tasks that implement the WorkerThread interface. * Each task may be named, but do not have to be. You may then waitOn * for ever task to complete or just the named tasks you care about... * or not wait at all. * * @author Jared Richardson * @version $Id$ */ public class ThreadQueue extends Thread { private static final Logger LOG = Logger.getLogger(ThreadQueue.class); // A ThreadGroup that logs uncaught exception using Log4J private final ThreadGroup loggingGroup = new Log4jThreadGroup("Logging group", LOG); /** * The list of WorkerThreads that are waiting to run (currently idle) */ private final List<WorkerThread> idleTasks = Collections.synchronizedList(new LinkedList<WorkerThread>()); /** * The list of WorkerThreads that are running now (currently busy) */ private final List<WorkerThread> busyTasks = Collections.synchronizedList(new LinkedList<WorkerThread>()); /** * the resultList from each WorkerThread's run */ private final Map<String, Object> resultList = Collections.synchronizedMap(new HashMap<String, Object>()); /** * Retains a handle to all the running Threads * to handle all sorts of interesting situations */ private final Map<WorkerThread, Thread> runningThreads = Collections.synchronizedMap(new HashMap<WorkerThread, Thread>()); /** * The number of java.lang.Threads to be launched by the pool at one time */ private final int threadCount = ThreadQueueProperties.getMaxThreadCount(); /** * The amount of time (millis) to sleep between loops */ private static final int SLEEP_TIME = 100; /** * A handle to the ThreadQueue singleton */ private static ThreadQueue threadQueue; /* fetch tasks to be executed from the idle list, put them on the busy list, and execute them */ public void run() { while (true) { final boolean nothingWaiting = idleTasks.size() == 0; final boolean maxedOut = busyTasks.size() >= threadCount; if (nothingWaiting || maxedOut) { sleep(SLEEP_TIME); } else { handleWaitingTask(); } cleanCompletedTasks(); } } private void handleWaitingTask() { LOG.debug("handling waiting task"); synchronized (busyTasks) { synchronized (idleTasks) { final WorkerThread firstIdleWorkerThread = idleTasks.get(0); //Since idleTasks allows duplicates, lets make sure this project is not already building if (getBusyTask(firstIdleWorkerThread.getName()) != null) { LOG.debug("The idle task is already running, it will not be moved to busy tasks yet"); return; } else { idleTasks.remove(firstIdleWorkerThread); } final Thread thisThread = new Thread(loggingGroup, firstIdleWorkerThread); busyTasks.add(firstIdleWorkerThread); runningThreads.put(firstIdleWorkerThread, thisThread); thisThread.start(); } } } private void cleanCompletedTasks() { synchronized (busyTasks) { final Iterator<WorkerThread> tasks = busyTasks.iterator(); while (tasks.hasNext()) { final WorkerThread task = tasks.next(); final Object result = task.getResult(); final boolean taskDone = result != null; if (taskDone) { LOG.debug("Found a finished task"); LOG.debug("tempTask.getName() = " + task.getName()); LOG.debug("tempTask.getResult() = " + task.getResult()); resultList.put(task.getName(), result); tasks.remove(); runningThreads.remove(task); } } } } /** * An internal wrapper around the creation of the * Thread Pool singleton * @return ThreadQueuse singleton */ private static ThreadQueue getThreadQueue() { if (threadQueue == null) { threadQueue = new ThreadQueue(); threadQueue.start(); } return threadQueue; } /** * Adds a task to the idleList to be executed * @param task a task to add to the idleList to be executed */ public static void addTask(final WorkerThread task) { LOG.debug("Preparing to add worker task " + task.getName()); synchronized (getThreadQueue().busyTasks) { synchronized (getThreadQueue().idleTasks) { // don't trust that 100 ms main loop managed to clean up very // recently finished tasks getThreadQueue().cleanCompletedTasks(); getThreadQueue().idleTasks.add(task); } } } /** * This may not *always* work -- a task may slip by us between queue checks. * That's OK. We'd rather have transient results than block the busy queue * until we're done just to get a position report on a task. * @param taskName the taskName to look for * @return the tasks position in the queue */ public static String findPosition(final String taskName) { WorkerThread task = getIdleTask(taskName); if (task != null) { return getTaskPosition(task, getThreadQueue().idleTasks, "IDLE"); } task = getBusyTask(taskName); if (task != null) { return getTaskPosition(task, getThreadQueue().busyTasks, "BUSY"); } final Object result = getResult(taskName); if (result != null) { return "[ COMPLETE ]"; } return "[ not found in queues ]"; } private static String getTaskPosition(final WorkerThread task, final List queue, final String queueName) { final int position; final int length; synchronized (getThreadQueue().busyTasks) { position = queue.indexOf(task); length = queue.size(); } return formatPosition(position, length, queueName); } private static String formatPosition(final int position, final int length, final String queueName) { if (position < 0) { return "[ NONE ]"; } // position is 0-based, make it 1-based for human reporting return queueName + "[ " + (position + 1) + " / " + length + " ]"; } /** * Checks to see if a specific task is either running or waiting in our system. * * @param taskName the task name to check * @return TRUE if task is waiting or running, FALSE if it is finished */ public static boolean isActive(final String taskName) { synchronized (getThreadQueue().busyTasks) { // it's either busy or idle return !((getBusyTask(taskName) == null) && (getIdleTask(taskName) == null)); } } /** * fetch a result from a completed WorkerThread * a null result means it's not done yet. * * @param workerName the worker name to check * @return a result from a completed WorkerThread, or null if not yet done. */ private static Object getResult(final String workerName) { return getThreadQueue().resultList.get(workerName); } /** * retrieves an active task from the busy list. * * @param taskName the task name to check * @return the active task (if present) or null if it cannot be found */ private static WorkerThread getBusyTask(final String taskName) { synchronized (getThreadQueue().busyTasks) { return getTask(taskName, getThreadQueue().busyTasks.iterator()); } } /** * retrieves an idle task from the idle list. * * @param taskName the task name to check * @return the idle task (if present) or null if it cannot be found */ private static WorkerThread getIdleTask(final String taskName) { synchronized (getThreadQueue().idleTasks) { return getTask(taskName, getThreadQueue().idleTasks.iterator()); } } /** * retrieves a task from the list * * @param taskName the task name to get * @param taskIter a WorkerThread iterator * @return the task (if present) or null if it cannot be found */ private static WorkerThread getTask(final String taskName, final Iterator<WorkerThread> taskIter) { while (taskIter.hasNext()) { final WorkerThread thisWorker = taskIter.next(); final String tempString = thisWorker.getName(); if (tempString.equalsIgnoreCase(taskName)) { return thisWorker; } } return null; } /** * @return the names of the tasks in the busy list; may be empty */ public static List<String> getBusyTaskNames() { final List<String> names; synchronized (getThreadQueue().busyTasks) { names = getTaskNames(getThreadQueue().busyTasks.iterator()); } return names; } /** * @return the names of the tasks in the idle list; may be empty */ public static List<String> getIdleTaskNames() { final List<String> names; synchronized (getThreadQueue().busyTasks) { names = getTaskNames(getThreadQueue().idleTasks.iterator()); } return names; } /** * @param taskIter a WorkerThread iterator * @return the names of the tasks in the list; may be empty */ private static List<String> getTaskNames(final Iterator<WorkerThread> taskIter) { final List<String> names = new LinkedList<String>(); while (taskIter.hasNext()) { final WorkerThread thisWorker = taskIter.next(); names.add(thisWorker.getName()); } return names; } /** * Utility call for sleeps. * @param ms milliseconds to sleep. */ private static void sleep(final int ms) { try { Thread.sleep(ms); } catch (Exception ignored) { } } static void stopQueue() { threadQueue.interrupt(); threadQueue = null; } }