bboss.org.jgroups.util.TimeScheduler Maven / Gradle / Ivy
The newest version!
package bboss.org.jgroups.util;
import java.util.concurrent.Delayed;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import bboss.org.jgroups.Global;
import bboss.org.jgroups.logging.Log;
import bboss.org.jgroups.logging.LogFactory;
/**
* Fixed-delay & fixed-rate single thread scheduler
*
* The scheduler supports varying scheduling intervals by asking the task
* every time for its next preferred scheduling interval. Scheduling can
* either be fixed-delay or fixed-rate. The notions are
* borrowed from java.util.Timer and retain the same meaning.
* I.e. in fixed-delay scheduling, the task's new schedule is calculated
* as:
* new_schedule = time_task_starts + scheduling_interval
*
* In fixed-rate scheduling, the next schedule is calculated as:
* new_schedule = time_task_was_supposed_to_start + scheduling_interval
*
* The scheduler internally holds a queue (DelayQueue) of tasks sorted in ascending order
* according to their next execution time. A task is removed from the queue
* if it is cancelled, i.e. if TimeScheduler.Task.isCancelled()
* returns true.
*
* The scheduler extends ScheduledThreadPoolExecutor to keep tasks
* sorted. java.util.Timer uses an array arranged as a binary heap (DelayQueue).
*
* Initially, the scheduler is in SUSPENDed mode, start()
* need not be called: if a task is added, the scheduler gets started
* automatically. Calling start() starts the scheduler if it's
* suspended or stopped else has no effect. Once stop() is called,
* added tasks will not restart it: start() has to be called to
* restart the scheduler.
* @author Bela Ban
* @version $Id: TimeScheduler.java,v 1.33 2009/11/05 08:43:34 belaban Exp $
*/
public class TimeScheduler extends ScheduledThreadPoolExecutor implements ThreadManager {
/** The interface that submitted tasks must implement */
public interface Task extends Runnable {
/** @return the next schedule interval. If <= 0 the task will not be re-scheduled */
long nextInterval();
}
/** How many core threads */
private static int TIMER_DEFAULT_NUM_THREADS=3;
protected static final Log log=LogFactory.getLog(TimeScheduler.class);
static {
String tmp;
try {
tmp=System.getProperty(Global.TIMER_NUM_THREADS);
if(tmp != null)
TIMER_DEFAULT_NUM_THREADS=Integer.parseInt(tmp);
}
catch(Exception e) {
log.error("could not set number of timer threads", e);
}
}
private ThreadDecorator threadDecorator=null;
/**
* Create a scheduler that executes tasks in dynamically adjustable intervals
*/
public TimeScheduler() {
this(TIMER_DEFAULT_NUM_THREADS);
}
public TimeScheduler(ThreadFactory factory) {
this(factory, TIMER_DEFAULT_NUM_THREADS);
}
public TimeScheduler(ThreadFactory factory, int max_threads) {
super(max_threads, factory);
setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(getRejectedExecutionHandler()));
}
public TimeScheduler(int corePoolSize) {
super(corePoolSize);
setRejectedExecutionHandler(new ShutdownRejectedExecutionHandler(getRejectedExecutionHandler()));
}
public ThreadDecorator getThreadDecorator() {
return threadDecorator;
}
public void setThreadDecorator(ThreadDecorator threadDecorator) {
this.threadDecorator=threadDecorator;
}
public String dumpTaskQueue() {
return getQueue().toString();
}
/**
* Schedule a task for execution at varying intervals. After execution, the task will get rescheduled after
* {@link bboss.org.jgroups.util.TimeScheduler.Task#nextInterval()} milliseconds. The task is neve done until nextInterval()
* return a value <= 0 or the task is cancelled.
* @param task the task to execute
* @param relative scheduling scheme: true:
* Task is rescheduled relative to the last time it actually started execution
* false:
Task is scheduled relative to its last execution schedule. This has the effect
* that the time between two consecutive executions of the task remains the same.
* Note that relative is always true; we always schedule the next execution relative to the last *actual*
* (not scheduled) execution
*/
public ScheduledFuture> scheduleWithDynamicInterval(Task task) {
if(task == null)
throw new NullPointerException();
if (isShutdown())
return null;
TaskWrapper task_wrapper=new TaskWrapper(task);
task_wrapper.doSchedule(); // calls schedule() in ScheduledThreadPoolExecutor
return task_wrapper;
}
public ScheduledFuture> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
return super.scheduleWithFixedDelay(new RobustRunnable(command), initialDelay, delay, unit);
}
/**
* Answers the number of tasks currently in the queue.
* @return The number of tasks currently in the queue.
*/
public int size() {
return getQueue().size();
}
/**
* Stop the scheduler if it's running. Switch to stopped, if it's
* suspended. Clear the task queue, cancelling all un-executed tasks
*
* @throws InterruptedException if interrupted while waiting for thread
* to return
*/
public void stop() throws InterruptedException {
java.util.List tasks=shutdownNow();
for(Runnable task: tasks) {
if(task instanceof Future) {
Future future=(Future)task;
future.cancel(true);
}
}
getQueue().clear();
awaitTermination(Global.THREADPOOL_SHUTDOWN_WAIT_TIME, TimeUnit.MILLISECONDS);
}
@Override
protected void afterExecute(Runnable r, Throwable t)
{
try {
super.afterExecute(r, t);
}
finally {
if(threadDecorator != null)
threadDecorator.threadReleased(Thread.currentThread());
}
}
/**
* Class which catches exceptions in run() - https://jira.jboss.org/jira/browse/JGRP-1062
*/
static class RobustRunnable implements Runnable {
final Runnable command;
public RobustRunnable(Runnable command) {
this.command=command;
}
public void run() {
if(command != null) {
try {
command.run();
}
catch(Throwable t) {
if(log.isErrorEnabled())
log.error("exception executing task " + command + ": " + t);
}
}
}
}
private class TaskWrapper implements Runnable, ScheduledFuture {
private final Task task;
private volatile ScheduledFuture> future; // cannot be null !
private volatile boolean cancelled=false;
public TaskWrapper(Task task) {
this.task=task;
}
public ScheduledFuture> getFuture() {
return future;
}
public void run() {
try {
if(cancelled) {
if(future != null)
future.cancel(true);
return;
}
if(future != null && future.isCancelled())
return;
task.run();
}
catch(Throwable t) {
log.error("failed running task " + task, t);
}
if(cancelled) {
if(future != null)
future.cancel(true);
return;
}
if(future != null && future.isCancelled())
return;
doSchedule();
}
public void doSchedule() {
long next_interval=task.nextInterval();
if(next_interval <= 0) {
if(log.isTraceEnabled())
log.trace("task will not get rescheduled as interval is " + next_interval);
}
else {
future=schedule(this, next_interval, TimeUnit.MILLISECONDS);
if(cancelled)
future.cancel(true);
}
}
public int compareTo(Delayed o) {
long my_delay=future.getDelay(TimeUnit.MILLISECONDS), their_delay=o.getDelay(TimeUnit.MILLISECONDS);
return my_delay < their_delay? -1 : my_delay > their_delay? 1 : 0;
}
public long getDelay(TimeUnit unit) {
return future != null? future.getDelay(unit) : -1;
}
public boolean cancel(boolean mayInterruptIfRunning) {
cancelled=true;
if(future != null)
future.cancel(mayInterruptIfRunning);
return cancelled;
}
public boolean isCancelled() {
return cancelled || (future != null && future.isCancelled());
}
public boolean isDone() {
return future == null || future.isDone();
}
public V get() throws InterruptedException, ExecutionException {
return null;
}
public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
}
}