/******************************************************************************* * ALMA - Atacama Large Millimiter Array * (c) European Southern Observatory, 2002 * Copyright by ESO (in the framework of the ALMA collaboration) * and Cosylab 2002, All rights reserved * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package alma.ACS.jbaci; import java.util.Date; import java.util.concurrent.ThreadFactory; /** * BACI timer. * Based on <code>EDU.oswego.cs.dl.util.concurrent</code> (it was not approriate for BACI usage). * Timer tasks should complete quickly. If a timer task takes excessive time to complete, * it "hogs" the timer's task execution thread. This can, in turn, delay the execution of * subsequent tasks, which may "bunch up" and execute in rapid succession when (and if) * the offending task finally completes. * @see EDU.oswego.cs.dl.util.concurrent * @author <a href="mailto:matej.sekoranjaATcosylab.com">Matej Sekoranja</a> * @version $id$ */ public class BACITimer { /** * Constructor. * @param threadFactory thread factory to be used to create thread, if <code>null</code> no factory is being used */ public BACITimer(ThreadFactory threadFactory) { threadFactory_ = threadFactory; runLoop_ = new RunLoop(); } /** tasks are maintained in a standard priority queue **/ protected final Heap heap_ = new Heap(64); /** thread factory, can be null **/ protected final ThreadFactory threadFactory_; /** shutdown status flag **/ protected volatile boolean shutdown_ = false; /** * Timer runnable interface. */ public interface TimerRunnable { /** * Method invoked by timer at requested time. */ public void timeout(long timeToRun); } protected static class TaskNode implements Comparable { final TimerRunnable command; // The command to run final long period; // The cycle period, or -1 if not periodic private long timeToRun_; // The time to run command // Cancellation does not immediately remove node, it just // sets up lazy deletion bit, so is thrown away when next // encountered in run loop private boolean cancelled_ = false; // Access to cancellation status and and run time needs sync // since they can be written and read in different threads synchronized void setCancelled() { cancelled_ = true; } synchronized boolean getCancelled() { return cancelled_; } synchronized void setTimeToRun(long w) { timeToRun_ = w; } synchronized long getTimeToRun() { return timeToRun_; } public int compareTo(Object other) { long a = getTimeToRun(); long b = ((TaskNode)(other)).getTimeToRun(); return (a < b)? -1 : ((a == b)? 0 : 1); } TaskNode(long w, TimerRunnable c, long p) { timeToRun_ = w; command = c; period = p; } TaskNode(long w, TimerRunnable c) { this(w, c, -1); } } /** * Execute the given command at the given time. * @param date -- the absolute time to run the command, expressed * as a java.util.Date. * @param command -- the command to run at the given time. * @return taskID -- an opaque reference that can be used to cancel execution request **/ public Object executeAt(Date date, TimerRunnable command) { TaskNode task = new TaskNode(date.getTime(), command); heap_.insert(task); restart(); return task; } /** * Excecute the given command after waiting for the given delay. * @param millisecondsToDelay -- the number of milliseconds * from now to run the command. * @param command -- the command to run after the delay. * @return taskID -- an opaque reference that can be used to cancel execution request **/ public Object executeAfterDelay(long millisecondsToDelay, TimerRunnable command) { long runtime = System.currentTimeMillis() + millisecondsToDelay; TaskNode task = new TaskNode(runtime, command); heap_.insert(task); restart(); return task; } /** * Execute the given command every <code>period</code> milliseconds AT FIXED RATE. * If <code>startNow</code> is true, execution begins immediately, * otherwise, it begins after the first <code>period</code> delay. * @param period -- the period, in milliseconds. Periods are * measured from start-of-task to the next start-of-task. It is * generally a bad idea to use a period that is shorter than * the expected task duration. * @param command -- the command to run at each cycle * @param firstTime -- time when task should start with execution, 0 means immediately. * @exception IllegalArgumentException if period less than or equal to zero. * @return taskID -- an opaque reference that can be used to cancel execution request **/ // msekoran: added firstTime support public Object executePeriodically(long period, TimerRunnable command, long firstTime) { if (period <= 0) throw new IllegalArgumentException(); if (firstTime == 0) firstTime = System.currentTimeMillis(); TaskNode task = new TaskNode(firstTime, command, period); heap_.insert(task); restart(); return task; } /** * Cancel a scheduled task that has not yet been run. * The task will be cancelled * upon the <em>next</em> opportunity to run it. This has no effect if * this is a one-shot task that has already executed. * Also, if an execution is in progress, it will complete normally. * (It may however be interrupted via getThread().interrupt()). * But if it is a periodic task, future iterations are cancelled. * @param taskID -- a task reference returned by one of * the execute commands * @exception ClassCastException if the taskID argument is not * of the type returned by an execute command. **/ public static void cancel(Object taskID) { ((TaskNode)taskID).setCancelled(); } /** The thread used to process commands **/ protected Thread thread_; /** * Return the thread being used to process commands, or * null if there is no such thread. You can use this * to invoke any special methods on the thread, for * example, to interrupt it. **/ public synchronized Thread getThread() { return thread_; } /** set thread_ to null to indicate termination **/ protected synchronized void clearThread() { thread_ = null; } /** * Start (or restart) a thread to process commands, or wake * up an existing thread if one is already running. This * method can be invoked if the background thread crashed * due to an unrecoverable exception in an executed command. **/ protected synchronized void restart() { if (shutdown_) { return; } if (thread_ == null) { if (threadFactory_ != null) { thread_ = threadFactory_.newThread(runLoop_); } else { thread_ = new Thread(runLoop_); thread_.setName(this.getClass().getName()); } thread_.setDaemon(true); // to ensure that this is a daemon thread thread_.start(); } else { notify(); } } /** * Cancel all tasks and interrupt the background thread executing * the current task, if any. * A new background thread will be started if new execution * requests are encountered. If the currently executing task * does not repsond to interrupts, the current thread may persist, even * if a new thread is started via restart(). **/ public synchronized void shutDown() { shutdown_ = true; heap_.clear(); if (thread_ != null) thread_.interrupt(); thread_ = null; } /** Return the next task to execute, or null if thread is interrupted **/ protected synchronized TaskNode nextTask() { // Note: This code assumes that there is only one run loop thread try { while (!Thread.interrupted()) { // Using peek simplifies dealing with spurious wakeups TaskNode task = (TaskNode)(heap_.peek()); if (task == null) { wait(); } else { long now = System.currentTimeMillis(); long when = task.getTimeToRun(); if (when > now) { // false alarm wakeup wait(when - now); } else { task = (TaskNode)(heap_.extract()); if (!task.getCancelled()) { // Skip if cancelled by if (task.period > 0) { // If periodic, requeue // msekoran: using fixed rate scheduling task.setTimeToRun(when + task.period); //task.setTimeToRun(now + task.period); heap_.insert(task); } return task; } } } } } catch (InterruptedException ex) { } // fall through return null; // on interrupt } /** * The runloop is isolated in its own Runnable class * just so that the main * class need not implement Runnable, which would * allow others to directly invoke run, which is not supported. **/ protected class RunLoop implements Runnable { public void run() { try { for (;;) { TaskNode task = nextTask(); if (task != null) // msekoran added timeToRun parameter task.command.timeout(task.getTimeToRun() - task.period); //task.command.run(); else break; } } finally { clearThread(); } } } protected final RunLoop runLoop_; }