* 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:
@ -34,7 +34,6 @@ import java.util.Properties;
|
|||||||
import net.i2p.client.I2PClient;
|
import net.i2p.client.I2PClient;
|
||||||
import net.i2p.client.streaming.RetransmissionTimer;
|
import net.i2p.client.streaming.RetransmissionTimer;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleTimer;
|
|
||||||
/**
|
/**
|
||||||
* <span style="font-size:8px;font-family:courier;color:#EEEEEE;background-color:#000000">
|
* <span style="font-size:8px;font-family:courier;color:#EEEEEE;background-color:#000000">
|
||||||
* ################################################################################<br>
|
* ################################################################################<br>
|
||||||
@ -151,7 +150,7 @@ public class BOB {
|
|||||||
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
|
String configLocation = System.getProperty(PROP_CONFIG_LOCATION, "bob.config");
|
||||||
|
|
||||||
// This is here just to ensure there is no interference with our threadgroups.
|
// This is here just to ensure there is no interference with our threadgroups.
|
||||||
SimpleTimer Y = RetransmissionTimer.getInstance();
|
RetransmissionTimer Y = RetransmissionTimer.getInstance();
|
||||||
i = Y.hashCode();
|
i = Y.hashCode();
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
package net.i2p.BOB;
|
package net.i2p.BOB;
|
||||||
|
|
||||||
import net.i2p.client.streaming.RetransmissionTimer;
|
import net.i2p.client.streaming.RetransmissionTimer;
|
||||||
import net.i2p.util.SimpleTimer;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start from command line
|
* Start from command line
|
||||||
@ -39,8 +38,8 @@ public class Main {
|
|||||||
*/
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// THINK THINK THINK THINK THINK THINK
|
// THINK THINK THINK THINK THINK THINK
|
||||||
SimpleTimer Y = RetransmissionTimer.getInstance();
|
RetransmissionTimer Y = RetransmissionTimer.getInstance();
|
||||||
BOB.main(args);
|
BOB.main(args);
|
||||||
Y.removeSimpleTimer();
|
Y.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,7 @@ import net.i2p.data.Destination;
|
|||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleScheduler;
|
import net.i2p.util.SimpleScheduler;
|
||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleTimer;
|
||||||
|
import net.i2p.util.SimpleTimer2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Maintain the state controlling a streaming connection between two
|
* Maintain the state controlling a streaming connection between two
|
||||||
@ -69,6 +70,7 @@ public class Connection {
|
|||||||
/** how many messages have been resent and not yet ACKed? */
|
/** how many messages have been resent and not yet ACKed? */
|
||||||
private int _activeResends;
|
private int _activeResends;
|
||||||
private ConEvent _connectionEvent;
|
private ConEvent _connectionEvent;
|
||||||
|
private int _randomWait;
|
||||||
|
|
||||||
private long _lifetimeBytesSent;
|
private long _lifetimeBytesSent;
|
||||||
private long _lifetimeBytesReceived;
|
private long _lifetimeBytesReceived;
|
||||||
@ -124,6 +126,7 @@ public class Connection {
|
|||||||
_isInbound = false;
|
_isInbound = false;
|
||||||
_updatedShareOpts = false;
|
_updatedShareOpts = false;
|
||||||
_connectionEvent = new ConEvent();
|
_connectionEvent = new ConEvent();
|
||||||
|
_randomWait = _context.random().nextInt(10*1000); // just do this once to reduce usage
|
||||||
_context.statManager().createRateStat("stream.con.windowSizeAtCongestion", "How large was our send window when we send a dup?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
_context.statManager().createRateStat("stream.con.windowSizeAtCongestion", "How large was our send window when we send a dup?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||||
_context.statManager().createRateStat("stream.chokeSizeBegin", "How many messages were outstanding when we started to choke?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
_context.statManager().createRateStat("stream.chokeSizeBegin", "How many messages were outstanding when we started to choke?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||||
_context.statManager().createRateStat("stream.chokeSizeEnd", "How many messages were outstanding when we stopped being choked?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
_context.statManager().createRateStat("stream.chokeSizeEnd", "How many messages were outstanding when we stopped being choked?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||||
@ -325,7 +328,8 @@ public class Connection {
|
|||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Resend in " + timeout + " for " + packet, new Exception("Sent by"));
|
_log.debug("Resend in " + timeout + " for " + packet, new Exception("Sent by"));
|
||||||
|
|
||||||
RetransmissionTimer.getInstance().addEvent(new ResendPacketEvent(packet, timeout + _context.clock().now()), timeout);
|
// schedules itself
|
||||||
|
ResendPacketEvent rpe = new ResendPacketEvent(packet, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
_context.statManager().getStatLog().addData(Packet.toId(_sendStreamId), "stream.rtt", _options.getRTT(), _options.getWindowSize());
|
_context.statManager().getStatLog().addData(Packet.toId(_sendStreamId), "stream.rtt", _options.getRTT(), _options.getWindowSize());
|
||||||
@ -526,7 +530,7 @@ public class Connection {
|
|||||||
if (_receiver != null)
|
if (_receiver != null)
|
||||||
_receiver.destroy();
|
_receiver.destroy();
|
||||||
if (_activityTimer != null)
|
if (_activityTimer != null)
|
||||||
SimpleTimer.getInstance().removeEvent(_activityTimer);
|
_activityTimer.cancel();
|
||||||
//_activityTimer = null;
|
//_activityTimer = null;
|
||||||
if (_inputStream != null)
|
if (_inputStream != null)
|
||||||
_inputStream.streamErrorOccurred(new IOException("disconnected!"));
|
_inputStream.streamErrorOccurred(new IOException("disconnected!"));
|
||||||
@ -822,15 +826,18 @@ public class Connection {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long howLong = _options.getInactivityTimeout();
|
long howLong = _options.getInactivityTimeout();
|
||||||
howLong += _context.random().nextInt(30*1000); // randomize it a bit, so both sides don't do it at once
|
howLong += _randomWait; // randomize it a bit, so both sides don't do it at once
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Resetting the inactivity timer to " + howLong, new Exception(toString()));
|
_log.debug("Resetting the inactivity timer to " + howLong, new Exception(toString()));
|
||||||
// this will get rescheduled, and rescheduled, and rescheduled...
|
// this will get rescheduled, and rescheduled, and rescheduled...
|
||||||
RetransmissionTimer.getInstance().removeEvent(_activityTimer);
|
_activityTimer.reschedule(howLong, false); // use the later of current and previous timeout
|
||||||
RetransmissionTimer.getInstance().addEvent(_activityTimer, howLong);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ActivityTimer implements SimpleTimer.TimedEvent {
|
private class ActivityTimer extends SimpleTimer2.TimedEvent {
|
||||||
|
public ActivityTimer() {
|
||||||
|
super(RetransmissionTimer.getInstance());
|
||||||
|
setFuzz(5*1000); // sloppy timer, don't reschedule unless at least 5s later
|
||||||
|
}
|
||||||
public void timeReached() {
|
public void timeReached() {
|
||||||
// uh, nothing more to do...
|
// uh, nothing more to do...
|
||||||
if (!_connected) {
|
if (!_connected) {
|
||||||
@ -841,7 +848,7 @@ public class Connection {
|
|||||||
long left = getTimeLeft();
|
long left = getTimeLeft();
|
||||||
if (left > 0) {
|
if (left > 0) {
|
||||||
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is time left (" + left + ")");
|
if (_log.shouldLog(Log.DEBUG)) _log.debug("Inactivity timeout reached, but there is time left (" + left + ")");
|
||||||
RetransmissionTimer.getInstance().addEvent(ActivityTimer.this, left);
|
schedule(left);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// these are either going to time out or cause further rescheduling
|
// these are either going to time out or cause further rescheduling
|
||||||
@ -1010,14 +1017,17 @@ public class Connection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Coordinate the resends of a given packet
|
* Coordinate the resends of a given packet
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
public class ResendPacketEvent implements SimpleTimer.TimedEvent {
|
public class ResendPacketEvent extends SimpleTimer2.TimedEvent {
|
||||||
private PacketLocal _packet;
|
private PacketLocal _packet;
|
||||||
private long _nextSendTime;
|
private long _nextSendTime;
|
||||||
public ResendPacketEvent(PacketLocal packet, long sendTime) {
|
public ResendPacketEvent(PacketLocal packet, long delay) {
|
||||||
|
super(RetransmissionTimer.getInstance());
|
||||||
_packet = packet;
|
_packet = packet;
|
||||||
_nextSendTime = sendTime;
|
_nextSendTime = delay + _context.clock().now();
|
||||||
packet.setResendPacketEvent(ResendPacketEvent.this);
|
packet.setResendPacketEvent(ResendPacketEvent.this);
|
||||||
|
schedule(delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getNextSendTime() { return _nextSendTime; }
|
public long getNextSendTime() { return _nextSendTime; }
|
||||||
@ -1025,6 +1035,10 @@ public class Connection {
|
|||||||
/**
|
/**
|
||||||
* Retransmit the packet if we need to.
|
* Retransmit the packet if we need to.
|
||||||
*
|
*
|
||||||
|
* ackImmediately() above calls directly in here, so
|
||||||
|
* we have to use forceReschedule() instead of schedule() below,
|
||||||
|
* to prevent duplicates in the timer queue.
|
||||||
|
*
|
||||||
* @param penalize true if this retransmission is caused by a timeout, false if we
|
* @param penalize true if this retransmission is caused by a timeout, false if we
|
||||||
* are just sending this packet instead of an ACK
|
* are just sending this packet instead of an ACK
|
||||||
* @return true if the packet was sent, false if it was not
|
* @return true if the packet was sent, false if it was not
|
||||||
@ -1057,7 +1071,7 @@ public class Connection {
|
|||||||
if (_log.shouldLog(Log.INFO))
|
if (_log.shouldLog(Log.INFO))
|
||||||
_log.info("Delaying resend of " + _packet + " as there are "
|
_log.info("Delaying resend of " + _packet + " as there are "
|
||||||
+ _activeResends + " active resends already in play");
|
+ _activeResends + " active resends already in play");
|
||||||
RetransmissionTimer.getInstance().addEvent(ResendPacketEvent.this, 1000);
|
forceReschedule(1000);
|
||||||
_nextSendTime = 1000 + _context.clock().now();
|
_nextSendTime = 1000 + _context.clock().now();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1144,7 +1158,7 @@ public class Connection {
|
|||||||
|
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Scheduling resend in " + timeout + "ms for " + _packet);
|
_log.debug("Scheduling resend in " + timeout + "ms for " + _packet);
|
||||||
RetransmissionTimer.getInstance().addEvent(ResendPacketEvent.this, timeout);
|
forceReschedule(timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
// acked during resending (... or somethin')
|
// acked during resending (... or somethin')
|
||||||
|
@ -8,7 +8,7 @@ import net.i2p.I2PAppContext;
|
|||||||
import net.i2p.data.ByteArray;
|
import net.i2p.data.ByteArray;
|
||||||
import net.i2p.util.ByteCache;
|
import net.i2p.util.ByteCache;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleTimer2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A stream that we can shove data into that fires off those bytes
|
* A stream that we can shove data into that fires off those bytes
|
||||||
@ -200,13 +200,20 @@ public class MessageOutputStream extends OutputStream {
|
|||||||
* Flush data that has been enqued but not flushed after a certain
|
* Flush data that has been enqued but not flushed after a certain
|
||||||
* period of inactivity
|
* period of inactivity
|
||||||
*/
|
*/
|
||||||
private class Flusher implements SimpleTimer.TimedEvent {
|
private class Flusher extends SimpleTimer2.TimedEvent {
|
||||||
private boolean _enqueued;
|
private boolean _enqueued;
|
||||||
|
public Flusher() {
|
||||||
|
super(RetransmissionTimer.getInstance());
|
||||||
|
}
|
||||||
public void enqueue() {
|
public void enqueue() {
|
||||||
// no need to be overly worried about duplicates - it would just
|
// no need to be overly worried about duplicates - it would just
|
||||||
// push it further out
|
// push it further out
|
||||||
if (!_enqueued) {
|
if (!_enqueued) {
|
||||||
RetransmissionTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
// Maybe we could just use schedule() here - or even SimpleScheduler - not sure...
|
||||||
|
// To be safe, use forceReschedule() so we don't get lots of duplicates
|
||||||
|
// We've seen the queue blow up before, maybe it was this before the rewrite...
|
||||||
|
// So perhaps it IS wise to be "overly worried" ...
|
||||||
|
forceReschedule(_passiveFlushDelay);
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Enqueueing the flusher for " + _passiveFlushDelay + "ms out");
|
_log.debug("Enqueueing the flusher for " + _passiveFlushDelay + "ms out");
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,7 +6,7 @@ import net.i2p.I2PAppContext;
|
|||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.data.SessionKey;
|
import net.i2p.data.SessionKey;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleTimer2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* coordinate local attributes about a packet - send time, ack time, number of
|
* coordinate local attributes about a packet - send time, ack time, number of
|
||||||
@ -27,7 +27,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
|||||||
private long _cancelledOn;
|
private long _cancelledOn;
|
||||||
private volatile int _nackCount;
|
private volatile int _nackCount;
|
||||||
private volatile boolean _retransmitted;
|
private volatile boolean _retransmitted;
|
||||||
private SimpleTimer.TimedEvent _resendEvent;
|
private SimpleTimer2.TimedEvent _resendEvent;
|
||||||
|
|
||||||
public PacketLocal(I2PAppContext ctx, Destination to) {
|
public PacketLocal(I2PAppContext ctx, Destination to) {
|
||||||
this(ctx, to, null);
|
this(ctx, to, null);
|
||||||
@ -93,7 +93,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
|||||||
releasePayload();
|
releasePayload();
|
||||||
notifyAll();
|
notifyAll();
|
||||||
}
|
}
|
||||||
SimpleTimer.getInstance().removeEvent(_resendEvent);
|
_resendEvent.cancel();
|
||||||
}
|
}
|
||||||
public void cancelled() {
|
public void cancelled() {
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
@ -101,11 +101,11 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
|||||||
releasePayload();
|
releasePayload();
|
||||||
notifyAll();
|
notifyAll();
|
||||||
}
|
}
|
||||||
SimpleTimer.getInstance().removeEvent(_resendEvent);
|
_resendEvent.cancel();
|
||||||
if (_log.shouldLog(Log.DEBUG))
|
if (_log.shouldLog(Log.DEBUG))
|
||||||
_log.debug("Cancelled! " + toString(), new Exception("cancelled"));
|
_log.debug("Cancelled! " + toString(), new Exception("cancelled"));
|
||||||
}
|
}
|
||||||
public SimpleTimer.TimedEvent getResendEvent() { return _resendEvent; }
|
public SimpleTimer2.TimedEvent getResendEvent() { return _resendEvent; }
|
||||||
|
|
||||||
/** how long after packet creation was it acked?
|
/** how long after packet creation was it acked?
|
||||||
* @return how long after packet creation the packet was ACKed in ms
|
* @return how long after packet creation the packet was ACKed in ms
|
||||||
@ -122,15 +122,15 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
|||||||
|
|
||||||
public void incrementNACKs() {
|
public void incrementNACKs() {
|
||||||
int cnt = ++_nackCount;
|
int cnt = ++_nackCount;
|
||||||
SimpleTimer.TimedEvent evt = _resendEvent;
|
SimpleTimer2.TimedEvent evt = _resendEvent;
|
||||||
if ( (cnt >= Connection.FAST_RETRANSMIT_THRESHOLD) && (evt != null) && (!_retransmitted)) {
|
if ( (cnt >= Connection.FAST_RETRANSMIT_THRESHOLD) && (evt != null) && (!_retransmitted)) {
|
||||||
_retransmitted = true;
|
_retransmitted = true;
|
||||||
RetransmissionTimer.getInstance().addEvent(evt, 0);
|
evt.reschedule(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public int getNACKs() { return _nackCount; }
|
public int getNACKs() { return _nackCount; }
|
||||||
|
|
||||||
public void setResendPacketEvent(SimpleTimer.TimedEvent evt) { _resendEvent = evt; }
|
public void setResendPacketEvent(SimpleTimer2.TimedEvent evt) { _resendEvent = evt; }
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StringBuffer formatAsString() {
|
public StringBuffer formatAsString() {
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
package net.i2p.client.streaming;
|
package net.i2p.client.streaming;
|
||||||
|
|
||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleTimer2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class RetransmissionTimer extends SimpleTimer {
|
public class RetransmissionTimer extends SimpleTimer2 {
|
||||||
private static final RetransmissionTimer _instance = new RetransmissionTimer();
|
private static final RetransmissionTimer _instance = new RetransmissionTimer();
|
||||||
public static final SimpleTimer getInstance() { return _instance; }
|
public static final RetransmissionTimer getInstance() { return _instance; }
|
||||||
protected RetransmissionTimer() { super("StreamingTimer"); }
|
protected RetransmissionTimer() { super("StreamingTimer"); }
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package net.i2p.client.streaming;
|
|||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleScheduler;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base scheduler
|
* Base scheduler
|
||||||
@ -17,6 +17,6 @@ abstract class SchedulerImpl implements TaskScheduler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void reschedule(long msToWait, Connection con) {
|
protected void reschedule(long msToWait, Connection con) {
|
||||||
SimpleTimer.getInstance().addEvent(con.getConnectionEvent(), msToWait);
|
SimpleScheduler.getInstance().addEvent(con.getConnectionEvent(), msToWait);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
import net.i2p.data.Destination;
|
import net.i2p.data.Destination;
|
||||||
import net.i2p.util.Log;
|
import net.i2p.util.Log;
|
||||||
import net.i2p.util.SimpleTimer;
|
import net.i2p.util.SimpleTimer2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Share important TCP Control Block parameters across Connections
|
* Share important TCP Control Block parameters across Connections
|
||||||
@ -38,11 +38,11 @@ public class TCBShare {
|
|||||||
_log = ctx.logManager().getLog(TCBShare.class);
|
_log = ctx.logManager().getLog(TCBShare.class);
|
||||||
_cache = new ConcurrentHashMap(4);
|
_cache = new ConcurrentHashMap(4);
|
||||||
_cleaner = new CleanEvent();
|
_cleaner = new CleanEvent();
|
||||||
SimpleTimer.getInstance().addEvent(_cleaner, CLEAN_TIME);
|
_cleaner.schedule(CLEAN_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void stop() {
|
public void stop() {
|
||||||
SimpleTimer.getInstance().removeEvent(_cleaner);
|
_cleaner.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateOptsFromShare(Connection con) {
|
public void updateOptsFromShare(Connection con) {
|
||||||
@ -124,14 +124,16 @@ public class TCBShare {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class CleanEvent implements SimpleTimer.TimedEvent {
|
private class CleanEvent extends SimpleTimer2.TimedEvent {
|
||||||
public CleanEvent() {}
|
public CleanEvent() {
|
||||||
|
super(RetransmissionTimer.getInstance());
|
||||||
|
}
|
||||||
public void timeReached() {
|
public void timeReached() {
|
||||||
for (Iterator iter = _cache.keySet().iterator(); iter.hasNext(); ) {
|
for (Iterator iter = _cache.keySet().iterator(); iter.hasNext(); ) {
|
||||||
if (_cache.get(iter.next()).isExpired())
|
if (_cache.get(iter.next()).isExpired())
|
||||||
iter.remove();
|
iter.remove();
|
||||||
}
|
}
|
||||||
SimpleTimer.getInstance().addEvent(CleanEvent.this, CLEAN_TIME);
|
schedule(CLEAN_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,8 +13,8 @@ import net.i2p.I2PAppContext;
|
|||||||
* appropriate time. The method that is fired however should NOT block (otherwise
|
* appropriate time. The method that is fired however should NOT block (otherwise
|
||||||
* they b0rk the timer).
|
* they b0rk the timer).
|
||||||
*
|
*
|
||||||
* This is like SimpleScheduler but addEvent() for an existing event adds a second
|
* This is like SimpleTimer but addEvent() for an existing event adds a second
|
||||||
* job. Events cannot be cancelled or rescheduled.
|
* job. Unlike SimpleTimer, events cannot be cancelled or rescheduled.
|
||||||
*
|
*
|
||||||
* For events that cannot or will not be cancelled or rescheduled -
|
* For events that cannot or will not be cancelled or rescheduled -
|
||||||
* for example, a call such as:
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,7 +6,12 @@ import net.i2p.util.SimpleTimer;
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class FlushTimer extends SimpleTimer {
|
class FlushTimer extends SimpleTimer {
|
||||||
private static final FlushTimer _instance = new FlushTimer();
|
/*
|
||||||
public static final SimpleTimer getInstance() { return _instance; }
|
Streaming lib has been moved from SimpleTimer to SimpleTimer2, eliminating the congestion.
|
||||||
protected FlushTimer() { super("TunnelFlushTimer"); }
|
So there's not much left using SimpleTimer, and FlushTimer doesn't need its own 4 threads any more
|
||||||
|
(if it ever did?...)
|
||||||
|
*/
|
||||||
|
//private static final FlushTimer _instance = new FlushTimer();
|
||||||
|
//public static final SimpleTimer getInstance() { return _instance; }
|
||||||
|
//protected FlushTimer() { super("TunnelFlushTimer"); }
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user