/* * Created on May 5, 2004 */ package cyrille.thread; import java.util.Timer; import java.util.TimerTask; import org.apache.commons.lang.IllegalClassException; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * <p> * Wrapper to add a Timeout to the {@link java.lang.Runnable#run()}method of the underlying * <code>runnable</code> * </p> * <p> * Relies on * <ul> * <li>{@link Thread#interrupt()}when a timeout occurs</li> * <li>scheduling one {@link TimerTask}per instance of <code>timedRunnable</code> to the given * {@link java.util.Timer}</li> * </ul> * </p> * * @author <a href="mailto:cleclerc@pobox.com">Cyrille Le Clerc </a> */ public class TimedRunnable implements Runnable, Comparable, Interruptible { /** * <p> * Used to interrupt the thread if timeout occurs * </p> * * @author <a href="mailto:cleclerc@pobox.com">Cyrille Le Clerc </a> */ public static class ThreadInterruptorTask extends TimerTask { private final static Log log = LogFactory.getLog(ThreadInterruptorTask.class); private Thread m_thread; /** * */ public ThreadInterruptorTask(Thread thread) { super(); this.m_thread = thread; } /** * @see java.lang.Runnable#run() */ @Override public void run() { boolean traceEnabled = log.isTraceEnabled(); if (traceEnabled) { log.trace("> run[" + this.toString() + "]"); } // cleanup the new thread using an appropriate means // eg. stop(), interrupt() or whatever log.trace("Interrupt thread : " + this.m_thread.getName()); this.m_thread.interrupt(); // TODO Should we more violently kill the thread ? if (traceEnabled) { log.trace("< run"); } } /** * @see java.lang.Object#toString() */ @Override public String toString() { return new ToStringBuilder(this).append("thread", this.m_thread.getName()).toString(); } } private final static Log log = LogFactory.getLog(TimedRunnable.class); private long m_timeoutInMillis; private Runnable m_runnable; private boolean m_timedOut; private Timer m_timer; /** * @param runnable * underlying <code>runnable</code> to execute * @param timeoutInMillis * @param timer * Timer to handle timeouts with a {@link TimerTask}per TimedRunnabled instance * */ public TimedRunnable(Runnable runnable, long timeoutInMillis, Timer timer) { super(); this.m_runnable = runnable; this.m_timeoutInMillis = timeoutInMillis; this.m_timer = timer; } /** * <p> * Compares the underlying <code>TimedRunnable#m_runnable<code> with :<br> * - if the other object is a <code>TimedRunnable</code>, the other <code>TimedRunnable#m_runnable<code> * - otherwise with the other object * </p> * @see java.lang.Comparable#compareTo(java.lang.Object) */ public int compareTo(Object o) { if (!(this.m_runnable instanceof Comparable)) { throw new IllegalClassException(Comparable.class, this.m_runnable.getClass()); } Comparable comparable = (Comparable) this.m_runnable; Comparable otherComparable; if (o instanceof TimedRunnable) { TimedRunnable otherTimedRunnable = (TimedRunnable) o; if (!(otherTimedRunnable.m_runnable instanceof Comparable)) { throw new IllegalClassException(Comparable.class, otherTimedRunnable.m_runnable.getClass()); } otherComparable = (Comparable) otherTimedRunnable.m_runnable; } else if (!(o instanceof Comparable)) { throw new IllegalClassException(Comparable.class, o.getClass()); } else { otherComparable = (Comparable) o; } return comparable.compareTo(otherComparable); } /** * @return Returns the timedOut. */ public boolean isTimedOut() { return this.m_timedOut; } /** * @see java.lang.Runnable#run() */ public void run() { boolean traceEnabled = log.isTraceEnabled(); if (traceEnabled) { log.trace("> run " + this.toString()); } ThreadInterruptorTask task = new ThreadInterruptorTask(Thread.currentThread()); this.m_timer.schedule(task, this.m_timeoutInMillis); boolean interrupted = false; try { this.m_runnable.run(); } catch (RuntimeException e) { int i = ExceptionUtils.indexOfThrowable(e, InterruptedException.class); if (i == -1) { throw e; } else { interrupted = true; this.m_timedOut = true; if (traceEnabled) { log.trace("Ignore InterruptedException " + this.toString()); } } } if (!interrupted) { boolean cancelled = task.cancel(); if (!cancelled) { log.error("Cancel failed, TimerTask is no longer scheduled : " + this.toString()); } } if (traceEnabled) { log.trace("< run "); } } /** * @see java.lang.Object#toString() */ @Override public String toString() { return new ToStringBuilder(this).append("timeoutInMillis", this.m_timeoutInMillis).append("runnable", this.m_runnable).append( "currentThread", Thread.currentThread().getName()).toString(); } /** * @see cyrille.thread.Interruptible#interrupt() */ public void interrupt() { boolean traceEnabled = log.isTraceEnabled(); if (traceEnabled) { log.trace("> interrupt"); } if (this.m_runnable instanceof Interruptible) { Interruptible interruptible = (Interruptible) this.m_runnable; interruptible.interrupt(); } if (traceEnabled) { log.trace("< interrupt"); } } }