package freenet.support; import java.util.Hashtable; import java.util.Timer; import java.util.TimerTask; import freenet.node.FastRunnable; /** * Ticker implemented using Timer's. * * If deploying this to replace PacketSender, be careful to handle priority changes properly. * Hopefully that can be achieved simply by creating at max priority during startup. * * @author Matthew Toseland <toad@amphibian.dyndns.org> (0xE43DA450) * */ public class TrivialTicker implements Ticker { private final Timer timer = new Timer(true); private final Executor executor; private final Hashtable<Runnable, TimerTask> jobs = new Hashtable<Runnable, TimerTask>(); private boolean running = true; public TrivialTicker(Executor executor) { this.executor = executor; } @Override public void queueTimedJob(final Runnable job, long offset) { TimerTask t = new TimerTask() { @Override public void run() { synchronized(TrivialTicker.this) { jobs.remove(job); // We must do this before job.run() in case the job re-schedules itself. } if(job instanceof FastRunnable) { job.run(); } else { executor.execute(job, "Delayed task: "+job); } } }; synchronized(this) { if(!running) return; timer.schedule(t, offset); jobs.put(job, t); } } @Override public void queueTimedJob(final Runnable job, final String name, long offset, boolean runOnTickerAnyway, boolean noDupes) { TimerTask t = new TimerTask() { @Override public void run() { synchronized(TrivialTicker.this) { jobs.remove(job); // We must do this before job.run() in case the job re-schedules itself. } if(job instanceof FastRunnable) { job.run(); } else { executor.execute(job, name); } } }; synchronized(this) { if(!running) return; if(noDupes && jobs.containsKey(job)) return; timer.schedule(t, offset); jobs.put(job, t); } } public void cancelTimedJob(final Runnable job) { removeQueuedJob(job); } @Override public void removeQueuedJob(final Runnable job) { synchronized(this) { if(!running) return; TimerTask t = jobs.remove(job); if(t != null) { t.cancel(); } } } /** * Changes the offset of a already-queued job. * If the given job was not queued yet it will be queued nevertheless. */ public void rescheduleTimedJob(final Runnable job, final String name, long newOffset) { synchronized(this) { removeQueuedJob(job); queueTimedJob(job, name, newOffset, false, false); // Don't dupe-check, we are synchronized } } private Thread shutdownThread = null; public void shutdown() { synchronized(this) { running = false; timer.schedule(new TimerTask() { @Override public void run() { // According to the JavaDoc of cancel(), calling it inside a TimerTask guarantees that the task is the last one which is run. timer.cancel(); synchronized(TrivialTicker.this) { shutdownThread = Thread.currentThread(); TrivialTicker.this.notifyAll(); } } }, 0); while(shutdownThread == null) { try { wait(); } catch (InterruptedException e) { } // Valid to happen due to spurious wakeups } while(shutdownThread.isAlive()) { // Ignore InterruptedExceptions try { shutdownThread.join(); } catch (InterruptedException e) { Logger.error(this, "Got an unexpected InterruptedException", e); } } } } @Override public Executor getExecutor() { return executor; } @Override public void queueTimedJobAbsolute(Runnable runner, String name, long time, boolean runOnTickerAnyway, boolean noDupes) { queueTimedJobAbsolute(runner, name, time - System.currentTimeMillis(), runOnTickerAnyway, noDupes); } }