/* * Funambol is a mobile platform developed by Funambol, Inc. * Copyright (C) 2011 Funambol, Inc. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * 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 Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com. * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Funambol" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Funambol". */ package com.funambol.concurrent; import java.util.Vector; /** * Allows generic tasks to be scheduled using specific priorities. * Tasks scheduled with higher priorities cause running tasks to be suspended * and then resumed once they completes. This can happens only if the running * task is a ResumableTask, otherwise it will be enqueued and will start once * the running task completes. * Using setMaxThreads you can set the number of threads that can be used to * schedule tasks. Tasks are suspended when there are no more threads available * to run a specific task with higher priority. */ public class TaskExecutor { private final int DEFAULT_MAX_THREADS_COUNT = 3; public static final int PRIORITY_LOW = -1; public static final int PRIORITY_MEDIUM = 0; public static final int PRIORITY_HIGH = 1; private final Vector tasksThreadPool = new Vector(); private final TaskQueue taskQueue = new TaskQueue(); private static TaskExecutor instance = null; private int maxThreads = DEFAULT_MAX_THREADS_COUNT; public TaskExecutor() { } /** * Set the max number of threads used to schedule tasks. * @param count */ public void setMaxThreads(int count) { maxThreads = count; } /** * Schedule the given task with low priority. * @param task */ public void scheduleTask(Task task) { scheduleTaskWithPriority(task, PRIORITY_LOW); } /** * Schedule the given task with the given priority. * @param task * @param priority */ public void scheduleTaskWithPriority(Task task, int priority) { synchronized(tasksThreadPool) { PriorityTask ptask = new PriorityTask(task, priority); boolean started = startNewThreadedTask(ptask); if(!started) { enqueueTask(ptask); // If there is no room to run the given task, then we start looking // for a lower priority task to suspend. for(int i=0; i<tasksThreadPool.size(); i++) { ThreadedTask threadedTask = (ThreadedTask)tasksThreadPool.elementAt(i); if((threadedTask.getPriorityTask().getPriority() < ptask.getPriority())) { if(threadedTask.isResumable() && threadedTask.suspend()) { // Enqueue suspended task enqueueTask(threadedTask.getPriorityTask()); break; } } } } } } private void enqueueTask(PriorityTask task) { synchronized(taskQueue) { taskQueue.put(task); } } private boolean startNewThreadedTask(PriorityTask task) { synchronized(tasksThreadPool) { if(tasksThreadPool.size() < maxThreads) { ThreadedTask threadedTask = new ThreadedTask(task); tasksThreadPool.addElement(threadedTask); threadedTask.start(); return true; } else { return false; } } } private void taskCompleted(ThreadedTask threadedTask) { synchronized(tasksThreadPool) { // Make room and run a new task tasksThreadPool.remove(threadedTask); PriorityTask next = taskQueue.get(); if(next != null) { startNewThreadedTask(next); } } } private class ThreadedTask { private PriorityTask ptask; private TaskRunnable taskRunnable; private Thread taskThread; public ThreadedTask(PriorityTask task) { this.ptask = task; this.taskRunnable = new TaskRunnable(this); this.taskThread = new Thread(taskRunnable); } public PriorityTask getPriorityTask() { return ptask; } /** * Start the task from a new thread */ public void start() { taskThread.start(); } /** * @return whether the current task is resumable */ public boolean isResumable() { return (ptask.getTask() instanceof ResumableTask); } /** * Suspend the thread task * @return true if the task has been correctly suspended */ public boolean suspend() { if(isResumable()) { boolean suspended = ((ResumableTask)ptask.getTask()).suspend(); ptask.setSuspended(suspended); return suspended; } else { return false; } } } private class TaskRunnable implements Runnable { private ThreadedTask threadedTask; public TaskRunnable(ThreadedTask threadedTask) { this.threadedTask = threadedTask; } public void run() { PriorityTask ptask = threadedTask.getPriorityTask(); // Resume task if it has been previously suspended if(ptask.isSuspended() && threadedTask.isResumable()) { ptask.setSuspended(false); ResumableTask resumableTask = ((ResumableTask)ptask.getTask()); resumableTask.resume(); } else { ptask.getTask().run(); } taskCompleted(threadedTask); } } private class TaskQueue { private Vector internalQueue = new Vector(); public PriorityTask get() { if(internalQueue.size() > 0) { PriorityTask pt = (PriorityTask)internalQueue.elementAt(0); internalQueue.removeElementAt(0); return pt; } else { return null; } } public void put(PriorityTask task) { int index = 0; for(; index<internalQueue.size(); index++) { PriorityTask pt = (PriorityTask)internalQueue.elementAt(index); if(pt.getPriority() < task.getPriority()) { break; } } internalQueue.insertElementAt(task, index); } } private class PriorityTask { private Task task; private int priority; private boolean suspended; public PriorityTask(Task task, int priority) { this.task = task; this.priority = priority; this.suspended = false; } public Task getTask() { return task; } public int getPriority() { return priority; } public boolean isSuspended() { return suspended; } public void setSuspended(boolean suspended) { this.suspended = suspended; } } }