package com.robonobo.common.concurrent;
/*
* Robonobo Common Utils
* Copyright (C) 2008 Will Morton (macavity@well.com) & Ray Hilton (ray@wirestorm.net)
* 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 2
* 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import static java.lang.System.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* For executing a runnable after a specified amount of time, when that time will be reset frequently, eg a network
* response timeout; avoids repeated calls to the task executor.
*
* @author macavity
*/
public class Timeout implements Runnable {
protected static Log log = LogFactory.getLog(Timeout.class);
protected static DateFormat df = new SimpleDateFormat("HH:mm:ss:SSS");
protected ScheduledThreadPoolExecutor executor;
protected Runnable task;
protected long taskScheduledTime = -1;
protected long eventFireTime = -1;
protected NameGetter nameGetter;
protected ScheduledFuture<?> future;
protected final boolean debugLogging;
/**
* Use this NameGetter strangeness to allow the name of the timeout to change during its execution
*/
public Timeout(ScheduledThreadPoolExecutor executor, Runnable task, NameGetter nameGetter) {
this.executor = executor;
this.task = task;
if (nameGetter != null)
this.nameGetter = nameGetter;
else {
this.nameGetter = new NameGetter() {
public String getName() {
return "Unknown Timeout";
}
};
}
debugLogging = log.isDebugEnabled();
}
public Timeout(ScheduledThreadPoolExecutor executor, Runnable task) {
this(executor, task, null);
}
/**
* Starts or restarts the timer
*/
public synchronized void set(long timeoutMs) {
long newEventFireTime = currentTimeMillis() + timeoutMs;
if (newEventFireTime < taskScheduledTime) {
// If our new fire time is before the task is due to be run, we need to reschedule the task
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' cancelling currently-scheduled task");
future.cancel(false);
taskScheduledTime = -1;
}
eventFireTime = newEventFireTime;
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' setting fire time to " + df.format(eventFireTime));
if (taskScheduledTime < 0)
startTimer();
}
public synchronized void cancel() {
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' cancelling ");
clear();
if (future != null)
future.cancel(false);
taskScheduledTime = -1;
}
/**
* Stops the timer
*/
public synchronized void clear() {
eventFireTime = -1;
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' clearing timeout");
}
public synchronized boolean isTaskIsScheduled() {
return eventFireTime > currentTimeMillis();
}
/**
* Must only be called inside a sync block
*/
protected void startTimer() {
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' scheduling task to run at " + df.format(eventFireTime));
future = executor.schedule(this, eventFireTime - currentTimeMillis(), TimeUnit.MILLISECONDS);
taskScheduledTime = eventFireTime;
}
public void run() {
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' task running");
synchronized (this) {
taskScheduledTime = -1;
// If we're not yet scheduled to run, wait until we are
if (currentTimeMillis() < eventFireTime) {
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' - timeout moved, rescheduling");
startTimer();
return;
}
// If we've been cleared, just return
if (eventFireTime < 0) {
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' - timeout cleared, task returning");
return;
}
}
// Let's rock
if (debugLogging)
log.debug("Timeout '" + nameGetter.getName() + "' - FIRING");
task.run();
}
public interface NameGetter {
public String getName();
}
}