001: package org.jgroups.util;
002:
003: import org.apache.commons.logging.Log;
004: import org.apache.commons.logging.LogFactory;
005:
006: import java.util.Timer;
007: import java.util.TimerTask;
008: import java.util.Date;
009:
010: /**
011: * Fixed-delay & fixed-rate single thread scheduler
012: * <p/>
013: * The scheduler supports varying scheduling intervals by asking the task
014: * every time for its next preferred scheduling interval. Scheduling can
015: * either be <i>fixed-delay</i> or <i>fixed-rate</i>. The notions are
016: * borrowed from <tt>java.util.Timer</tt> and retain the same meaning.
017: * I.e. in fixed-delay scheduling, the task's new schedule is calculated
018: * as:<br>
019: * new_schedule = time_task_starts + scheduling_interval
020: * <p/>
021: * In fixed-rate scheduling, the next schedule is calculated as:<br>
022: * new_schedule = time_task_was_supposed_to_start + scheduling_interval
023: * <p/>
024: * The scheduler internally holds a queue of tasks sorted in ascending order
025: * according to their next execution time. A task is removed from the queue
026: * if it is cancelled, i.e. if <tt>TimeScheduler.Task.isCancelled()</tt>
027: * returns true.
028: * <p/>
029: * The scheduler internally uses a <tt>java.util.SortedSet</tt> to keep tasks
030: * sorted. <tt>java.util.Timer</tt> uses an array arranged as a binary heap
031: * that doesn't shrink. It is likely that the latter arrangement is faster.
032: * <p/>
033: * Initially, the scheduler is in <tt>SUSPEND</tt>ed mode, <tt>start()</tt>
034: * need not be called: if a task is added, the scheduler gets started
035: * automatically. Calling <tt>start()</tt> starts the scheduler if it's
036: * suspended or stopped else has no effect. Once <tt>stop()</tt> is called,
037: * added tasks will not restart it: <tt>start()</tt> has to be called to
038: * restart the scheduler.
039: * @author Bela Ban
040: * @version $Id: TimeScheduler.java,v 1.14.2.2 2007/04/27 09:11:18 belaban Exp $
041: */
042: public class TimeScheduler extends Timer {
043: /**
044: * The interface that submitted tasks must implement
045: */
046: public interface Task {
047: /**
048: * @return true if task is cancelled and shouldn't be scheduled
049: * again
050: */
051: boolean cancelled();
052:
053: /**
054: * @return the next schedule interval
055: */
056: long nextInterval();
057:
058: /**
059: * Execute the task
060: */
061: void run();
062: }
063:
064: public interface CancellableTask extends Task {
065: /**
066: * Cancels the task. After calling this, {@link #cancelled()} return true. If the task was already cancelled,
067: * this is a no-op
068: */
069: void cancel();
070: }
071:
072: private int size = 0; // maintains the number of tasks currently scheduled to execute
073:
074: protected static final Log log = LogFactory
075: .getLog(TimeScheduler.class);
076:
077: public TimeScheduler() {
078: super (true);
079: }
080:
081: public TimeScheduler(boolean isDaemon) {
082: super (isDaemon);
083: }
084:
085: public String dumpTaskQueue() {
086: return toString();
087: }
088:
089: /**
090: * Add a task for execution at adjustable intervals
091: * @param task the task to execute
092: * @param relative scheduling scheme:
093: * <p/>
094: * <tt>true</tt>:<br>
095: * Task is rescheduled relative to the last time it <i>actually</i>
096: * started execution
097: * <p/>
098: * <tt>false</tt>:<br>
099: * Task is scheduled relative to its <i>last</i> execution schedule. This
100: * has the effect that the time between two consecutive executions of
101: * the task remains the same.<p/>
102: * April 07: the relative argument is ignored, will always be true
103: */
104: public void add(Task task, boolean relative) {
105: TaskWrapper wrapper = new TaskWrapper(task);
106: schedule(wrapper, task.nextInterval());
107: }
108:
109: /**
110: * Add a task for execution at adjustable intervals
111: * @param t the task to execute
112: */
113: public void add(Task t) {
114: add(t, true);
115: }
116:
117: public void schedule(TimerTask task, long delay) {
118: super .schedule(task, delay);
119: size++;
120: }
121:
122: public void schedule(TimerTask task, long delay, long period) {
123: super .schedule(task, delay, period);
124: size++;
125: }
126:
127: public void schedule(TimerTask task, Date firstTime, long period) {
128: super .schedule(task, firstTime, period);
129: size++;
130: }
131:
132: public void schedule(TimerTask task, Date time) {
133: super .schedule(task, time);
134: size++;
135: }
136:
137: public void scheduleAtFixedRate(TimerTask task, long delay,
138: long period) {
139: super .scheduleAtFixedRate(task, delay, period);
140: size++;
141: }
142:
143: public void scheduleAtFixedRate(TimerTask task, Date firstTime,
144: long period) {
145: super .scheduleAtFixedRate(task, firstTime, period);
146: size++;
147: }
148:
149: public void cancel() {
150: super .cancel();
151: size = 0;
152: }
153:
154: /**
155: * Returns the number of tasks currently scheduled. Note that this is an approximation.
156: * @return The number of tasks currently in the queue.
157: */
158: public int size() {
159: return size;
160: }
161:
162: /**
163: * Start the scheduler, if it's suspended or stopped
164: */
165: public void start() {
166: ; // no-op
167: }
168:
169: /**
170: * Stop the scheduler if it's running. Switch to stopped, if it's
171: * suspended. Clear the task queue.
172: *
173: * @throws InterruptedException if interrupted while waiting for thread
174: * to return
175: */
176: public void stop() throws InterruptedException {
177: }
178:
179: private class TaskWrapper extends TimerTask {
180: private final Task delegate; // points to the user-submitted task
181:
182: public TaskWrapper(Task delegate) {
183: this .delegate = delegate;
184: }
185:
186: public void run() {
187: if (delegate.cancelled()) {
188: cancel();
189: return;
190: }
191: try {
192: delegate.run();
193: } catch (Throwable t) {
194: if (log.isWarnEnabled()) {
195: log.warn("exception executing task " + delegate, t);
196: }
197: }
198: size = Math.max(size - 1, 0);
199: if (!delegate.cancelled()) {
200: long next_interval = delegate.nextInterval();
201: TimerTask new_task = new TaskWrapper(delegate);
202: schedule(new_task, next_interval);
203: }
204: }
205:
206: public boolean cancel() {
207: size = Math.max(0, size - 1);
208: return super.cancel();
209: }
210: }
211: }
|