/*************************************************************************
*
* ADOBE CONFIDENTIAL
* __________________
*
* Copyright 2002 - 2007 Adobe Systems Incorporated
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe Systems Incorporated and its suppliers,
* if any. The intellectual and technical concepts contained
* herein are proprietary to Adobe Systems Incorporated
* and its suppliers and may be covered by U.S. and Foreign Patents,
* patents in process, and are protected by trade secret or copyright law.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe Systems Incorporated.
**************************************************************************/
package flex.messaging.util;
import flex.messaging.log.Log;
import flex.messaging.log.LogCategories;
import edu.emory.mathcs.backport.java.util.concurrent.Future;
import edu.emory.mathcs.backport.java.util.concurrent.ScheduledThreadPoolExecutor;
import edu.emory.mathcs.backport.java.util.concurrent.ThreadFactory;
import edu.emory.mathcs.backport.java.util.concurrent.TimeUnit;
/**
* This class provides a means of managing TimeoutCapable objects. It leverages
* facilities in the the Java concurrency package to provide a common utility
* for scheduling timeout Futures and managing the underlying worker thread pools.
*
* @author neville
* @exclude
*/
public class TimeoutManager
{
private static final String LOG_CATEGORY = LogCategories.TIMEOUT;
private ScheduledThreadPoolExecutor timeoutService;
/**
* Default constructor calls parameterized constructor will a null factory argument.
*/
public TimeoutManager()
{
this(null);
}
/**
* Constructs a new TimeoutManager using the passed in factory for thread creation.
*
* @param tf ThreadFactory
*/
public TimeoutManager(ThreadFactory tf)
{
if (tf == null)
{
tf = new MonitorThreadFactory();
}
timeoutService = new ScheduledThreadPoolExecutor(1, tf);
}
/**
* Schedule a task to be executed in the future.
*
* @param t task to be executed at some future time
* @return a Future object that enables access to the value(s) returned by the task
*/
public Future scheduleTimeout(TimeoutCapable t)
{
Future future = null;
if (t.getTimeoutPeriod() > 0)
{
Runnable timeoutTask = new TimeoutTask(t);
future = timeoutService.schedule(timeoutTask, t.getTimeoutPeriod(), TimeUnit.MILLISECONDS);
t.setTimeoutFuture(future);
if (t instanceof TimeoutAbstractObject)
{
TimeoutAbstractObject timeoutAbstract = (TimeoutAbstractObject)t;
timeoutAbstract.setTimeoutManager(this);
timeoutAbstract.setTimeoutTask(timeoutTask);
}
if (Log.isDebug())
Log.getLogger(LOG_CATEGORY).debug("TimeoutManager '" + System.identityHashCode(this) + "' has scheduled instance '" +
System.identityHashCode(t) + "' of type '" + t.getClass().getName() + "' to be timed out in " + t.getTimeoutPeriod() + " milliseconds. Task queue size: "+ timeoutService.getQueue().size());
}
return future;
}
/**
* Cancel the execution of a future task and remove all references to it.
*
* @param timeoutAbstract the task to be canceled
* @return true if cancellation were successful
*/
public boolean unscheduleTimeout(TimeoutAbstractObject timeoutAbstract)
{
Object toRemove = timeoutAbstract.getTimeoutFuture();
/*
* In more recent versions of the backport, they are requiring that we
* pass in the Future returned by the schedule method. This should always
* implement Runnable even in 2.2 but I'm a little paranoid here so just
* to be sure, if we get a future which is not a runnable we go back to the old
* code which calls the remove on the instance we passed into the schedule method.
*/
if (!(toRemove instanceof Runnable))
toRemove = timeoutAbstract.getTimeoutTask();
if (timeoutService.remove((Runnable) toRemove))
{
if (Log.isDebug())
Log.getLogger(LOG_CATEGORY).debug("TimeoutManager '" + System.identityHashCode(this) + "' has removed the timeout task for instance '" +
System.identityHashCode(timeoutAbstract) + "' of type '" + timeoutAbstract.getClass().getName() + "' that has requested its timeout be cancelled. Task queue size: "+ timeoutService.getQueue().size());
}
else
{
Future timeoutFuture = timeoutAbstract.getTimeoutFuture();
timeoutFuture.cancel(false); // Don't interrupt it if it's running.
if (Log.isDebug())
Log.getLogger(LOG_CATEGORY).debug("TimeoutManager '" + System.identityHashCode(this) + "' cancelling timeout task for instance '" +
System.identityHashCode(timeoutAbstract) + "' of type '" + timeoutAbstract.getClass().getName() + "' that has requested its timeout be cancelled. Task queue size: "+ timeoutService.getQueue().size());
if (timeoutFuture.isDone())
{
timeoutService.purge(); // Force the service to give up refs to task immediately rather than hanging on to them.
if (Log.isDebug())
Log.getLogger(LOG_CATEGORY).debug("TimeoutManager '" + System.identityHashCode(this) + "' purged queue of any cancelled or completed tasks. Task queue size: "+ timeoutService.getQueue().size());
}
}
// to aggressively clean up memory remove the reference from the unscheduled timeout to its
// time out object
Object unscheduledTimeoutTask = timeoutAbstract.getTimeoutTask();
if (unscheduledTimeoutTask != null && unscheduledTimeoutTask instanceof TimeoutTask)
((TimeoutTask)timeoutAbstract.getTimeoutTask()).clearTimeoutCapable();
return true;
}
/**
* Cancel any future tasks.
*/
public void shutdown()
{
timeoutService.shutdown();
}
class MonitorThreadFactory implements ThreadFactory
{
public Thread newThread(Runnable r)
{
Thread t = new Thread(r);
t.setDaemon(true);
t.setName("TimeoutManager");
return t;
}
}
class TimeoutTask implements Runnable
{
private TimeoutCapable timeoutObject;
/**
* Removes the reference from this timeout task to the object that would
* have been timed out. This is useful for memory clean up when timeouts are unscheduled.
*/
public void clearTimeoutCapable()
{
timeoutObject = null;
}
public TimeoutTask(TimeoutCapable timeoutObject)
{
this.timeoutObject = timeoutObject;
}
public void run()
{
long inactiveMillis = System.currentTimeMillis() - timeoutObject.getLastUse();
if (inactiveMillis >= timeoutObject.getTimeoutPeriod())
{
timeoutObject.timeout();
if (Log.isDebug())
Log.getLogger(LOG_CATEGORY).debug("TimeoutManager '" + System.identityHashCode(TimeoutManager.this) + "' has run the timeout task for instance '" +
System.identityHashCode(timeoutObject) + "' of type '" + timeoutObject.getClass().getName() + "'. Task queue size: "+ timeoutService.getQueue().size());
}
else
{
// Reschedule timeout and store new Future for cancellation.
timeoutObject.setTimeoutFuture(timeoutService.schedule(this, (timeoutObject.getTimeoutPeriod()-inactiveMillis), TimeUnit.MILLISECONDS));
if (Log.isDebug())
Log.getLogger(LOG_CATEGORY).debug("TimeoutManager '" + System.identityHashCode(TimeoutManager.this) + "' has rescheduled a timeout for the active instance '" +
System.identityHashCode(timeoutObject) + "' of type '" + timeoutObject.getClass().getName() + "'. Task queue size: "+ timeoutService.getQueue().size());
}
}
}
}