package net.i2p.util; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.ThreadFactory; import net.i2p.I2PAppContext; /** * Simple event scheduler - toss an event on the queue and it gets fired at the * appropriate time. The method that is fired however should NOT block (otherwise * they b0rk the timer). * * This is like SimpleTimer but addEvent() for an existing event adds a second * job. Unlike SimpleTimer, events cannot be cancelled or rescheduled. * * For events that cannot or will not be cancelled or rescheduled - * for example, a call such as: * SimpleTimer.getInstance().addEvent(new FooEvent(bar), timeoutMs); * use SimpleScheduler instead to reduce lock contention in SimpleTimer... * * For periodic events, use addPeriodicEvent(). Unlike SimpleTimer, * uncaught Exceptions will not prevent subsequent executions. * * @deprecated in 0.9.20, use SimpleTimer2 instead * * @author zzz */ @Deprecated public class SimpleScheduler { /** * If you have a context, use context.simpleScheduler() instead * @deprecated in 0.9.20, replaced by SimpleTimer2 */ @Deprecated public static SimpleScheduler getInstance() { return I2PAppContext.getGlobalContext().simpleScheduler(); } private static final int MIN_THREADS = 2; private static final int MAX_THREADS = 4; private final Log _log; private final ScheduledThreadPoolExecutor _executor; private final String _name; private int _count; private final int _threads; /** * To be instantiated by the context. * Others should use context.simpleTimer() instead * @deprecated in 0.9.20, replaced by SimpleTimer2 */ @Deprecated public SimpleScheduler(I2PAppContext context) { this(context, "SimpleScheduler"); } /** * To be instantiated by the context. * Others should use context.simpleTimer() instead * @deprecated in 0.9.20, replaced by SimpleTimer2 */ @Deprecated private SimpleScheduler(I2PAppContext context, String name) { _log = context.logManager().getLog(SimpleScheduler.class); _name = name; long maxMemory = SystemVersion.getMaxMemory(); _threads = (int) Math.max(MIN_THREADS, Math.min(MAX_THREADS, 1 + (maxMemory / (32*1024*1024)))); _executor = new ScheduledThreadPoolExecutor(_threads, new CustomThreadFactory()); _executor.prestartAllCoreThreads(); // don't bother saving ref to remove hook if somebody else calls stop context.addShutdownTask(new Shutdown()); } /** * @since 0.8.8 */ private class Shutdown implements Runnable { public void run() { stop(); } } /** * Stops the SimpleScheduler. * Subsequent executions should not throw a RejectedExecutionException. */ public void stop() { _executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); _executor.shutdownNow(); } /** * Queue up the given event to be fired no sooner than timeoutMs from now. * * @param event * @param timeoutMs */ public void addEvent(SimpleTimer.TimedEvent event, long timeoutMs) { if (event == null) throw new IllegalArgumentException("addEvent null"); RunnableEvent re = new RunnableEvent(event, timeoutMs); re.schedule(); } /** * Queue up the given event to be fired after timeoutMs and every * timeoutMs thereafter. The TimedEvent must not do its own rescheduling. * As all Exceptions are caught in run(), these will not prevent * subsequent executions (unlike SimpleTimer, where the TimedEvent does * its own rescheduling). */ public void addPeriodicEvent(SimpleTimer.TimedEvent event, long timeoutMs) { addPeriodicEvent(event, timeoutMs, timeoutMs); } /** * Queue up the given event to be fired after initialDelay and every * timeoutMs thereafter. The TimedEvent must not do its own rescheduling. * As all Exceptions are caught in run(), these will not prevent * subsequent executions (unlike SimpleTimer, where the TimedEvent does * its own rescheduling) * * @param event * @param initialDelay (ms) * @param timeoutMs */ public void addPeriodicEvent(SimpleTimer.TimedEvent event, long initialDelay, long timeoutMs) { if (event == null) throw new IllegalArgumentException("addEvent null"); RunnableEvent re = new PeriodicRunnableEvent(event, initialDelay, timeoutMs); re.schedule(); } private class CustomThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { Thread rv = Executors.defaultThreadFactory().newThread(r); rv.setName(_name + ' ' + (++_count) + '/' + _threads); // Uncomment this to test threadgrouping, but we should be all safe now that the constructor preallocates! // String name = rv.getThreadGroup().getName(); // if(!name.equals("main")) { // (new Exception("OWCH! DAMN! Wrong ThreadGroup `" + name +"', `" + rv.getName() + "'")).printStackTrace(); // } rv.setDaemon(true); return rv; } } /** * Same as SimpleTimer.TimedEvent but use run() instead of timeReached(), and remembers the time */ private class RunnableEvent implements Runnable { protected final SimpleTimer.TimedEvent _timedEvent; protected long _scheduled; public RunnableEvent(SimpleTimer.TimedEvent t, long timeoutMs) { if (_log.shouldLog(Log.DEBUG)) _log.debug("Creating w/ delay " + timeoutMs + " : " + t); _timedEvent = t; _scheduled = timeoutMs + System.currentTimeMillis(); } public void schedule() { _executor.schedule(this, _scheduled - System.currentTimeMillis(), TimeUnit.MILLISECONDS); } public void run() { if (_log.shouldLog(Log.DEBUG)) _log.debug("Running: " + _timedEvent); long before = System.currentTimeMillis(); if (_log.shouldLog(Log.WARN) && before < _scheduled - 100) _log.warn(_name + " early execution " + (_scheduled - before) + ": " + _timedEvent); else if (_log.shouldLog(Log.WARN) && before > _scheduled + 1000) _log.warn("late execution " + (before - _scheduled) + ": " + _timedEvent + debug()); try { _timedEvent.timeReached(); } catch (Throwable t) { _log.log(Log.CRIT, _name + ": Scheduled task " + _timedEvent + " exited unexpectedly, please report", t); } long time = System.currentTimeMillis() - before; if (time > 1000 && _log.shouldLog(Log.WARN)) _log.warn(_name + " event execution took " + time + ": " + _timedEvent); if (_log.shouldLog(Log.INFO)) { // this call is slow - iterates through a HashMap - // would be better to have a local AtomicLong if we care long completed = _executor.getCompletedTaskCount(); if (completed % 250 == 0) _log.info(debug()); } } } /** Run every timeoutMs. TimedEvent must not do its own reschedule via addEvent() */ private class PeriodicRunnableEvent extends RunnableEvent { private final long _timeoutMs; private final long _initialDelay; public PeriodicRunnableEvent(SimpleTimer.TimedEvent t, long initialDelay, long timeoutMs) { super(t, timeoutMs); _initialDelay = initialDelay; _timeoutMs = timeoutMs; _scheduled = initialDelay + System.currentTimeMillis(); } @Override public void schedule() { _executor.scheduleWithFixedDelay(this, _initialDelay, _timeoutMs, TimeUnit.MILLISECONDS); } @Override public void run() { super.run(); _scheduled = _timeoutMs + System.currentTimeMillis(); } } private String debug() { return " Pool: " + _name + " Active: " + _executor.getActiveCount() + '/' + _executor.getPoolSize() + " Completed: " + _executor.getCompletedTaskCount() + " Queued: " + _executor.getQueue().size(); } }