* Streaming lib:

- Move ConEvent from SimpleTimer to SimpleScheduler
      - Move RetransmissionTimer (ResendPacketEvent)
        from SimpleTimer to new SimpleTimer2
      - Move ActivityTimer and Flusher from SimpleTimer to RetransmissionTimer
      - SimpleTimer2 allows specifying "fuzz" to reduce
        timer queue churn further
This commit is contained in:
zzz
2009-02-15 05:11:35 +00:00
parent 7b12f700dd
commit cc3165bf72
11 changed files with 322 additions and 44 deletions

View File

@ -13,8 +13,8 @@ import net.i2p.I2PAppContext;
* appropriate time. The method that is fired however should NOT block (otherwise
* they b0rk the timer).
*
* This is like SimpleScheduler but addEvent() for an existing event adds a second
* job. Events cannot be cancelled or rescheduled.
* 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:

View File

@ -0,0 +1,252 @@
package net.i2p.util;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadFactory;
import java.util.Map;
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 rewrites the old SimpleTimer to use the java.util.concurrent.ScheduledThreadPoolExecutor.
* SimpleTimer has problems with lock contention;
* this should work a lot better.
*
* This supports cancelling and arbitrary rescheduling.
* If you don't need that, use SimpleScheduler instead.
*
* SimpleTimer is deprecated, use this or SimpleScheduler.
*
* @author zzz
*/
public class SimpleTimer2 {
private static final SimpleTimer2 _instance = new SimpleTimer2();
public static SimpleTimer2 getInstance() { return _instance; }
private static final int THREADS = 4;
private I2PAppContext _context;
private static Log _log; // static so TimedEvent can use it
private ScheduledThreadPoolExecutor _executor;
private String _name;
private int _count;
protected SimpleTimer2() { this("SimpleTimer2"); }
protected SimpleTimer2(String name) {
_context = I2PAppContext.getGlobalContext();
_log = _context.logManager().getLog(SimpleTimer2.class);
_name = name;
_count = 0;
_executor = new CustomScheduledThreadPoolExecutor(THREADS, new CustomThreadFactory());
}
/**
* Removes the SimpleTimer.
*/
public void stop() {
_executor.shutdownNow();
}
private class CustomScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
public CustomScheduledThreadPoolExecutor(int threads, ThreadFactory factory) {
super(threads, factory);
}
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
if (t != null) // shoudn't happen, caught in RunnableEvent.run()
_log.log(Log.CRIT, "wtf, event borked: " + r, t);
}
}
private class CustomThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
Thread rv = Executors.defaultThreadFactory().newThread(r);
rv.setName(_name + ' ' + (++_count) + '/' + THREADS);
rv.setDaemon(true);
return rv;
}
}
private ScheduledFuture schedule(TimedEvent t, long timeoutMs) {
return _executor.schedule(t, timeoutMs, TimeUnit.MILLISECONDS);
}
/**
* Similar to SimpleTimer.TimedEvent but users must extend instead of implement,
* and all schedule and cancel methods are through this class rather than SimpleTimer2.
*
* To convert over, change implements SimpleTimer.TimedEvent to extends SimpleTimer2.TimedEvent,
* and be sure to call super(SimpleTimer2.getInstance(), timeoutMs) in the constructor
* (or super(SimpleTimer2.getInstance()); .... schedule(timeoutMs); if there is other stuff
* in your constructor)
*
* Other porting:
* SimpleTimer.getInstance().addEvent(new foo(), timeout) => new foo(SimpleTimer2.getInstance(), timeout)
* SimpleTimer.getInstance().addEvent(this, timeout) => schedule(timeout)
* SimpleTimer.getInstance().addEvent(foo, timeout) => foo.reschedule(timeout)
* SimpleTimer.getInstance().removeEvent(foo) => foo.cancel()
*
* There's no global locking, but for scheduling, we synchronize on this
* to reduce the chance of duplicates on the queue.
*
* schedule(ms) can get create duplicates
* reschedule(ms) and reschedule(ms, true) can lose the timer
* reschedule(ms, false) and forceReschedule(ms) are relatively safe from either
*
*/
public static abstract class TimedEvent implements Runnable {
private SimpleTimer2 _pool;
private int _fuzz;
protected static final int DEFAULT_FUZZ = 3;
private ScheduledFuture _future; // _executor.remove() doesn't work so we have to use this
// ... and I expect cancelling this way is more efficient
/** must call schedule() later */
public TimedEvent(SimpleTimer2 pool) {
_pool = pool;
_fuzz = DEFAULT_FUZZ;
}
/** automatically schedules, don't use this one if you have other things to do first */
public TimedEvent(SimpleTimer2 pool, long timeoutMs) {
this(pool);
schedule(timeoutMs);
}
/**
* Don't bother rescheduling if +/- this many ms or less.
* Use this to reduce timer queue and object churn for a sloppy timer like
* an inactivity timer.
* Default 3 ms.
*/
public void setFuzz(int fuzz) {
_fuzz = fuzz;
}
/**
* More efficient than reschedule().
* Only call this after calling the non-scheduling constructor,
* or from within timeReached(), or you will get duplicates on the queue.
* Otherwise use reschedule().
*/
public synchronized void schedule(long timeoutMs) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Scheduling: " + this + " timeout = " + timeoutMs);
if (timeoutMs <= 0 && _log.shouldLog(Log.WARN))
timeoutMs = 1; // otherwise we may execute before _future is updated, which is fine
// except it triggers 'early execution' warning logging
_future = _pool.schedule(this, timeoutMs);
}
/**
* Use the earliest of the new time and the old time
* Do not call from within timeReached()
*
* @param timeoutMs
*/
public void reschedule(long timeoutMs) {
reschedule(timeoutMs, true);
}
/**
* useEarliestTime must be false if called from within timeReached(), as
* it won't be rescheduled, in favor of the currently running task
*
* @param timeoutMs
* @param useEarliestTime if its already scheduled, use the earlier of the
* two timeouts, else use the later
*/
public synchronized void reschedule(long timeoutMs, boolean useEarliestTime) {
long oldTimeout;
boolean scheduled = _future != null && !_future.isDone();
if (scheduled)
oldTimeout = _future.getDelay(TimeUnit.MILLISECONDS);
else
oldTimeout = timeoutMs;
// don't bother rescheduling if within _fuzz ms
if ((oldTimeout - _fuzz > timeoutMs && useEarliestTime) ||
(oldTimeout + _fuzz < timeoutMs && !useEarliestTime)||
(!scheduled)) {
if (scheduled) {
if (_log.shouldLog(Log.INFO))
_log.info("Re-scheduling: " + this + " timeout = " + timeoutMs + " old timeout was " + oldTimeout);
cancel();
}
schedule(timeoutMs);
}
}
/**
* Always use the new time - ignores fuzz
* @param timeoutMs
*/
public synchronized void forceReschedule(long timeoutMs) {
cancel();
schedule(timeoutMs);
}
/** returns true if cancelled */
public synchronized boolean cancel() {
if (_future == null)
return false;
return _future.cancel(false);
}
public void run() {
if (_log.shouldLog(Log.DEBUG))
_log.debug("Running: " + this);
long before = System.currentTimeMillis();
long delay = 0;
if (_future != null)
delay = _future.getDelay(TimeUnit.MILLISECONDS);
else if (_log.shouldLog(Log.WARN))
_log.warn(_pool + " wtf, no _future " + this);
// This can be an incorrect warning especially after a schedule(0)
if (_log.shouldLog(Log.WARN) && delay > 100)
_log.warn(_pool + " wtf, early execution " + delay + ": " + this);
else if (_log.shouldLog(Log.WARN) && delay < -1000)
_log.warn(" wtf, late execution " + delay + ": " + this + _pool.debug());
try {
timeReached();
} catch (Throwable t) {
_log.log(Log.CRIT, _pool + " wtf, event borked: " + this, t);
}
long time = System.currentTimeMillis() - before;
if (time > 500 && _log.shouldLog(Log.WARN))
_log.warn(_pool + " wtf, event execution took " + time + ": " + this);
long completed = _pool.getCompletedTaskCount();
if (_log.shouldLog(Log.INFO) && completed % 250 == 0)
_log.info(_pool.debug());
}
/**
* Simple interface for events to be queued up and notified on expiration
* the time requested has been reached (this call should NOT block,
* otherwise the whole SimpleTimer gets backed up)
*
*/
public abstract void timeReached();
}
public String toString() {
return _name;
}
private long getCompletedTaskCount() {
return _executor.getCompletedTaskCount();
}
private String debug() {
_executor.purge(); // Remove cancelled tasks from the queue so we get a good queue size stat
return
" Pool: " + _name +
" Active: " + _executor.getActiveCount() + '/' + _executor.getPoolSize() +
" Completed: " + _executor.getCompletedTaskCount() +
" Queued: " + _executor.getQueue().size();
}
}