* 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:
@ -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:
|
||||
|
252
core/java/src/net/i2p/util/SimpleTimer2.java
Normal file
252
core/java/src/net/i2p/util/SimpleTimer2.java
Normal 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();
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user