/* * This file is part of ADDIS (Aggregate Data Drug Information System). * ADDIS is distributed from http://drugis.org/. * Copyright (C) 2009 Gert van Valkenhoef, Tommi Tervonen. * Copyright (C) 2010 Gert van Valkenhoef, Tommi Tervonen, * Tijs Zwinkels, Maarten Jacobs, Hanno Koeslag, Florin Schimbinschi, * Ahmad Kamal, Daniel Reid. * * This program 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. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.drugis.common.threading; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.drugis.common.beans.AbstractObservable; import org.drugis.common.threading.event.TaskEvent; import org.drugis.common.threading.event.TaskEvent.EventType; public class ThreadHandler extends AbstractObservable { public static final String PROPERTY_RUNNING_THREADS = "runningThreads"; public static final String PROPERTY_QUEUED_TASKS = "queuedTasks"; public static final String PROPERTY_FAILED_TASK = "failedTask"; /** * Thread to start/suspend threads, limiting the number of running tasks to the number of cores available. */ private class RunQueueCleaner implements Runnable { public void run(){ while(true) { synchronized (d_runningTasks) { List<SuspendableThreadWrapper> toRun = getThreadsToRun(d_numCores); List<SuspendableThreadWrapper> toStop = new LinkedList<SuspendableThreadWrapper>(d_runningTasks); toStop.removeAll(toRun); toRun.removeAll(d_runningTasks); // toRun now contains the tasks to be started // Stop tasks that shouldn't be running, if they are Suspendable. // Otherwise they will remain running. for (SuspendableThreadWrapper t : toStop) { if (!t.isTerminated()) { if (t.isAborted() || t.suspend()) { d_runningTasks.remove(t); } } else { d_runningTasks.remove(t); } } // Fill up the runningTasks with additional tasks (at most d_numCores tasks). int availableSlots = d_numCores - d_runningTasks.size(); for (int i = 0; i < availableSlots && i < toRun.size(); ++i) { SuspendableThreadWrapper t = toRun.get(i); d_runningTasks.add(t); } // Ensure all tasks in the runningTasks list are started (also restarts previously suspended ones). for (SuspendableThreadWrapper t : d_runningTasks) { t.start(); } firePropertyChange(PROPERTY_QUEUED_TASKS, null, d_scheduledTasks.size()); firePropertyChange(PROPERTY_RUNNING_THREADS, null, d_runningTasks.size()); } try { Thread.sleep(200); } catch (InterruptedException e) { throw new RuntimeException(e); } } } } private class TaskFailureObserver implements TaskListener { public void taskEvent(TaskEvent event) { if (event.getType() == EventType.TASK_FAILED) { firePropertyChange(PROPERTY_FAILED_TASK, null, event); } } } private final int d_numCores; LinkedList<Task> d_scheduledTasks; LinkedList<SuspendableThreadWrapper> d_runningTasks; Thread d_cleaner; private TaskFailureObserver d_failureObserver = new TaskFailureObserver(); Map<SimpleTask, SuspendableThreadWrapper> d_wrappers = new HashMap<SimpleTask, SuspendableThreadWrapper>(); private static volatile ThreadHandler d_singleton; private ThreadHandler() { d_numCores = Runtime.getRuntime().availableProcessors(); d_scheduledTasks = new LinkedList<Task>(); d_runningTasks = new LinkedList<SuspendableThreadWrapper>(); d_cleaner = new Thread(new RunQueueCleaner()); d_cleaner.setDaemon(true); d_cleaner.start(); } /** * Get at most n tasks to be run, based on the queue of scheduled tasks. */ public List<SuspendableThreadWrapper> getThreadsToRun(int n) { List<SimpleTask> toRun = new ArrayList<SimpleTask>(n); for (int i = 0; i < d_scheduledTasks.size() && toRun.size() < n; ) { Task task = d_scheduledTasks.get(i); if (task.isFinished() || task.isFailed() || task.isAborted()) { d_scheduledTasks.remove(i); } else if (task instanceof SimpleTask) { add(toRun, (SimpleTask)task); ++i; } else if (task instanceof CompositeTask) { CompositeTask compositeTask = (CompositeTask)task; if (!compositeTask.isStarted()) { compositeTask.start(); } List<SimpleTask> next = compositeTask.getNextTasks(); for (int j = 0; j < next.size() && toRun.size() < n; ++j) { add(toRun, next.get(j)); } ++i; } else { throw new RuntimeException("Unhandled Task type: " + task.getClass().getName()); } } return getWrappers(toRun); } /** * Add only if unique. * @param toRun * @param simpleTask */ private void add(List<SimpleTask> toRun, SimpleTask simpleTask) { if (!toRun.contains(simpleTask)) { toRun.add(simpleTask); } } public static ThreadHandler getInstance() { if (d_singleton == null) { d_singleton = new ThreadHandler(); } return d_singleton; } public int getRunningThreads() { return d_runningTasks.size(); } public int getQueuedTasks() { return d_scheduledTasks.size(); } public void scheduleTask(Task r) { scheduleTasks(Collections.singleton(r)); } /** * Schedule tasks for execution. May also be used to re-prioritize already scheduled tasks (the new ones get highest priority). * @param newTasks tasks to schedule. */ public synchronized void scheduleTasks(Collection<? extends Task> newTasks) { synchronized (d_runningTasks) { // If tasks already present, reschedule d_scheduledTasks.removeAll(newTasks); // Put new tasks at the front of the queue d_scheduledTasks.addAll(0, newTasks); firePropertyChange(PROPERTY_QUEUED_TASKS, null, d_scheduledTasks.size()); } } /** * Wrap the given runnables in a thread, keeping track of which runnables have been wrapped. */ private LinkedList<SuspendableThreadWrapper> getWrappers(Collection<SimpleTask> runnables) { vacuumWrappers(); /* Check whether Runnable already has a wrapper, otherwise create it */ LinkedList<SuspendableThreadWrapper> newList = new LinkedList<SuspendableThreadWrapper>(); for (SimpleTask r : runnables) { SuspendableThreadWrapper w = d_wrappers.get(r); if (w == null) { w = new SuspendableThreadWrapper(r); r.addTaskListener(d_failureObserver); d_wrappers.put(r, w); } newList.add(w); } return newList; } /** * Remove unused thread wrappers. */ private void vacuumWrappers() { List<Task> toRemove = new ArrayList<Task>(d_wrappers.keySet().size()); for (Task r : d_wrappers.keySet()) { if (r.isFinished()) { toRemove.add(r); } } for (Task r : toRemove) { r.removeTaskListener(d_failureObserver); d_wrappers.remove(r); } } protected List<Task> getRunningTasks() { List<Task> tasks = new ArrayList<Task>(); synchronized(d_runningTasks) { for (SuspendableThreadWrapper w : d_runningTasks) { tasks.add((SimpleTask)w.getRunnable()); } } return tasks; } protected List<Task> getQueuedTaskList() { return Collections.unmodifiableList(d_scheduledTasks); } /** * Abort all scheduled tasks. */ public void clear() { synchronized(d_runningTasks) { terminateTasks(d_wrappers.values()); d_scheduledTasks.clear(); vacuumWrappers(); } firePropertyChange(PROPERTY_QUEUED_TASKS, null, d_scheduledTasks.size()); firePropertyChange(PROPERTY_RUNNING_THREADS, null, d_runningTasks.size()); } /** * Terminate the given threads. */ private void terminateTasks(Collection<SuspendableThreadWrapper> tasks) { for (SuspendableThreadWrapper thread : tasks) { thread.terminate(); } } /** * Abort the given task. */ public boolean abortTask(Task t) { if (t instanceof SimpleTask) { return abortSimple((SimpleTask) t); } else if (t instanceof CompositeTask) { return abortComposite((CompositeTask) t); } throw new IllegalArgumentException("Attempt to remove unknown task type: " + t.getClass().getCanonicalName()); } private boolean abortSimple(SimpleTask t) { boolean terminated = true; synchronized(d_runningTasks) { if (d_wrappers.get(t) != null) { terminated = d_wrappers.get(t).terminate(); } d_scheduledTasks.remove(t); vacuumWrappers(); } firePropertyChange(PROPERTY_QUEUED_TASKS, null, d_scheduledTasks.size()); firePropertyChange(PROPERTY_RUNNING_THREADS, null, d_runningTasks.size()); return terminated; } private boolean abortComposite(CompositeTask composite) { boolean terminated = true; synchronized(d_runningTasks) { d_scheduledTasks.remove(composite); if(composite.isStarted()) { for (SimpleTask task : composite.getNextTasks()) { if (!d_scheduledTasks.contains(task)) { if (d_wrappers.get(task) != null) { terminated = terminated && d_wrappers.get(task).terminate(); } } } } vacuumWrappers(); } firePropertyChange(PROPERTY_QUEUED_TASKS, null, d_scheduledTasks.size()); firePropertyChange(PROPERTY_RUNNING_THREADS, null, d_runningTasks.size()); return terminated ; } }