2004-12-03 jrandom
* Toss in a small pool of threads (3) to execute the events queued up with the SimpleTimer, as we do currently see the occational event notification spiking up to a second or so. * Implement a SAM client API in java, useful for event based streaming (or for testing the SAM bridge) * Added support to shut down the SAM bridge on OOM (useful if the SAM bridge is being run outside of the router). * Include the SAM test code in the sam.jar * Remove an irrelevent warning message from SAM, which was caused by perfectly normal operation due to a session being closed. * Removed some unnecessary synchronization in the streaming lib's PacketQueue * More quickly clean up the memory used by the streaming lib by immediately killing each packet's resend job as soon as it is ACKed (or cancelled), so that there are no longer any valid pointers to the (potentially 32KB) packet. * Fixed the timestamps dumped to stdout when debugging the PacketHandler. * Drop packets that would expand our inbound window beyond our maximum buffer size (default 32 messages) * Always read the ACK/NACK data from the verified packets received, even if we are going to drop them * Always adjust the window when there are messages ACKed, though do not change its size except as before. * Streamlined some synchronization in the router's I2CP handling * Streamlined some memory allocation in the SAM bridge * Default the streaming lib to disconnect on inactivity, rather than send an empty message. this still doesnt get the BT to where it needs to be, or fix the timeout problem, but i dont like having so many commits outstanding and these updates are sound
This commit is contained in:
@ -112,6 +112,8 @@ public class Connection {
|
||||
_connectLock = new Object();
|
||||
_activeResends = 0;
|
||||
_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.chokeSizeEnd", "How many messages were outstanding when we stopped being choked?", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
public long getNextOutboundPacketNum() {
|
||||
@ -135,9 +137,14 @@ public class Connection {
|
||||
boolean packetSendChoke(long timeoutMs) {
|
||||
if (false) return true;
|
||||
long writeExpire = timeoutMs;
|
||||
long start = _context.clock().now();
|
||||
boolean started = false;
|
||||
while (true) {
|
||||
long timeLeft = writeExpire - _context.clock().now();
|
||||
synchronized (_outboundPackets) {
|
||||
if (!started)
|
||||
_context.statManager().addRateData("stream.chokeSizeBegin", _outboundPackets.size(), timeoutMs);
|
||||
started = true;
|
||||
if (_outboundPackets.size() >= _options.getWindowSize()) {
|
||||
if (writeExpire > 0) {
|
||||
if (timeLeft <= 0) {
|
||||
@ -154,6 +161,7 @@ public class Connection {
|
||||
try { _outboundPackets.wait(); } catch (InterruptedException ie) {}
|
||||
}
|
||||
} else {
|
||||
_context.statManager().addRateData("stream.chokeSizeEnd", _outboundPackets.size(), _context.clock().now() - start);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -325,14 +333,19 @@ public class Connection {
|
||||
_occurredEventCount++;
|
||||
} else {
|
||||
_occurredTime = now;
|
||||
if (_occurredEventCount > 100) {
|
||||
_log.log(Log.CRIT, "More than 100 events (" + _occurredEventCount + ") in a second on "
|
||||
+ toString() + ": scheduler = " + sched);
|
||||
if ( (_occurredEventCount > 1000) && (_log.shouldLog(Log.WARN)) ) {
|
||||
_log.warn("More than 1000 events (" + _occurredEventCount + ") in a second on "
|
||||
+ toString() + ": scheduler = " + sched);
|
||||
}
|
||||
_occurredEventCount = 0;
|
||||
}
|
||||
|
||||
long before = System.currentTimeMillis();
|
||||
|
||||
sched.eventOccurred(this);
|
||||
long elapsed = System.currentTimeMillis() - before;
|
||||
if ( (elapsed > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took " + elapsed + "ms to pump through " + sched);
|
||||
}
|
||||
|
||||
void resetReceived() {
|
||||
@ -714,6 +727,7 @@ public class Connection {
|
||||
public ResendPacketEvent(PacketLocal packet) {
|
||||
_packet = packet;
|
||||
_currentIsActiveResend = false;
|
||||
packet.setResendPacketEvent(ResendPacketEvent.this);
|
||||
}
|
||||
|
||||
public void timeReached() {
|
||||
|
@ -64,7 +64,7 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
if (doSend) {
|
||||
PacketLocal packet = send(buf, off, size);
|
||||
//dont wait for non-acks
|
||||
if ( (packet.getPayloadSize() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
|
||||
if ( (packet.getSequenceNum() > 0) || (packet.isFlagSet(Packet.FLAG_SYNCHRONIZE)) )
|
||||
return packet;
|
||||
else
|
||||
return _dummyStatus;
|
||||
@ -95,8 +95,16 @@ class ConnectionDataReceiver implements MessageOutputStream.DataReceiver {
|
||||
* @return the packet sent
|
||||
*/
|
||||
public PacketLocal send(byte buf[], int off, int size, boolean forceIncrement) {
|
||||
long before = System.currentTimeMillis();
|
||||
PacketLocal packet = buildPacket(buf, off, size, forceIncrement);
|
||||
long built = System.currentTimeMillis();
|
||||
_connection.sendPacket(packet);
|
||||
long sent = System.currentTimeMillis();
|
||||
|
||||
if ( (built-before > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, took " + (built-before) + "ms to build a packet: " + packet);
|
||||
if ( (sent-built> 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("wtf, took " + (sent-built) + "ms to send a packet: " + packet);
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
@ -87,7 +87,7 @@ public class ConnectionOptions extends I2PSocketOptionsImpl {
|
||||
setMaxResends(getInt(opts, PROP_MAX_RESENDS, 5));
|
||||
setWriteTimeout(getInt(opts, PROP_WRITE_TIMEOUT, -1));
|
||||
setInactivityTimeout(getInt(opts, PROP_INACTIVITY_TIMEOUT, 5*60*1000));
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_SEND));
|
||||
setInactivityAction(getInt(opts, PROP_INACTIVITY_ACTION, INACTIVITY_ACTION_DISCONNECT));
|
||||
setInboundBufferSize((getMaxMessageSize() + 2) * Connection.MAX_WINDOW_SIZE);
|
||||
}
|
||||
|
||||
|
@ -30,12 +30,22 @@ public class ConnectionPacketHandler {
|
||||
/** distribute a packet to the connection specified */
|
||||
void receivePacket(Packet packet, Connection con) throws I2PException {
|
||||
boolean ok = verifyPacket(packet, con);
|
||||
if (!ok) return;
|
||||
if (!ok) {
|
||||
if ( (!packet.isFlagSet(Packet.FLAG_RESET)) && (_log.shouldLog(Log.ERROR)) )
|
||||
_log.error("Packet does NOT verify: " + packet);
|
||||
return;
|
||||
}
|
||||
con.packetReceived();
|
||||
|
||||
if (con.getInputStream().getTotalQueuedSize() > con.getOptions().getInboundBufferSize()) {
|
||||
long ready = con.getInputStream().getHighestReadyBockId();
|
||||
int available = con.getOptions().getInboundBufferSize() - con.getInputStream().getTotalReadySize();
|
||||
int allowedBlocks = available/con.getOptions().getMaxMessageSize();
|
||||
if (packet.getSequenceNum() > ready + allowedBlocks) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Inbound buffer exceeded on connection " + con + ": dropping " + packet);
|
||||
_log.warn("Inbound buffer exceeded on connection " + con + " ("
|
||||
+ ready + "/"+ (ready+allowedBlocks) + "/" + available
|
||||
+ ": dropping " + packet);
|
||||
ack(con, packet.getAckThrough(), packet.getNacks(), null, false);
|
||||
con.getOptions().setChoke(5*1000);
|
||||
return;
|
||||
}
|
||||
@ -95,8 +105,20 @@ public class ConnectionPacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
boolean fastAck = ack(con, packet.getAckThrough(), packet.getNacks(), packet, isNew);
|
||||
con.eventOccurred();
|
||||
if (fastAck) {
|
||||
if (con.getLastSendTime() + con.getOptions().getRTT() < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ack(Connection con, long ackThrough, long nacks[], Packet packet, boolean isNew) {
|
||||
int numResends = 0;
|
||||
List acked = con.ackPackets(packet.getAckThrough(), packet.getNacks());
|
||||
List acked = con.ackPackets(ackThrough, nacks);
|
||||
if ( (acked != null) && (acked.size() > 0) ) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug(acked.size() + " of our packets acked with " + packet);
|
||||
@ -130,18 +152,15 @@ public class ConnectionPacketHandler {
|
||||
_context.statManager().addRateData("stream.con.packetsAckedPerMessageReceived", acked.size(), highestRTT);
|
||||
}
|
||||
|
||||
boolean fastAck = adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
|
||||
con.eventOccurred();
|
||||
if (fastAck) {
|
||||
if (con.getLastSendTime() + con.getOptions().getRTT() < _context.clock().now()) {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Fast ack for dup " + packet);
|
||||
con.ackImmediately();
|
||||
}
|
||||
}
|
||||
if (packet != null)
|
||||
return adjustWindow(con, isNew, packet.getSequenceNum(), numResends, (acked != null ? acked.size() : 0));
|
||||
else
|
||||
return adjustWindow(con, false, -1, numResends, (acked != null ? acked.size() : 0));
|
||||
}
|
||||
|
||||
|
||||
private boolean adjustWindow(Connection con, boolean isNew, long sequenceNum, int numResends, int acked) {
|
||||
boolean congested = false;
|
||||
if ( (!isNew) && (sequenceNum > 0) ) {
|
||||
// dup real packet
|
||||
int oldSize = con.getOptions().getWindowSize();
|
||||
@ -156,64 +175,38 @@ public class ConnectionPacketHandler {
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
|
||||
return true;
|
||||
//} else if (numResends > 0) {
|
||||
// window sizes are shrunk on resend, not on ack
|
||||
} else {
|
||||
if (acked > 0) {
|
||||
long lowest = con.getHighestAckedThrough();
|
||||
if (lowest >= con.getCongestionWindowEnd()) {
|
||||
// new packet that ack'ed uncongested data, or an empty ack
|
||||
int newWindowSize = con.getOptions().getWindowSize();
|
||||
|
||||
if (numResends <= 0) {
|
||||
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
// congestion avoidance
|
||||
congested = true;
|
||||
}
|
||||
|
||||
long lowest = con.getHighestAckedThrough();
|
||||
if (lowest >= con.getCongestionWindowEnd()) {
|
||||
// new packet that ack'ed uncongested data, or an empty ack
|
||||
int newWindowSize = con.getOptions().getWindowSize();
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
// integers, so lets use a random distribution instead
|
||||
int shouldIncrement = _context.random().nextInt(newWindowSize);
|
||||
if (shouldIncrement <= 0)
|
||||
newWindowSize += 1;
|
||||
} else {
|
||||
// slow start
|
||||
newWindowSize += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
con.getOptions().setWindowSize(newWindowSize);
|
||||
con.setCongestionWindowEnd(newWindowSize + lowest);
|
||||
if ( (!congested) && (acked > 0) && (numResends <= 0) ) {
|
||||
if (newWindowSize > con.getLastCongestionSeenAt() / 2) {
|
||||
// congestion avoidance
|
||||
|
||||
// we can't use newWindowSize += 1/newWindowSize, since we're
|
||||
// integers, so lets use a random distribution instead
|
||||
int shouldIncrement = _context.random().nextInt(newWindowSize);
|
||||
if (shouldIncrement <= 0)
|
||||
newWindowSize += 1;
|
||||
} else {
|
||||
// slow start
|
||||
newWindowSize += 1;
|
||||
}
|
||||
} else {
|
||||
// received a message that doesn't contain a new ack
|
||||
|
||||
// ehh. cant do this, as we SACK and the acks may be
|
||||
// received out of order:
|
||||
// Alice: RECEIVE 2
|
||||
// Alice: SEND ack 2 nack 1
|
||||
// Alice: RECEIVE 1
|
||||
// Alice: SEND ack 2
|
||||
// Bob: RECEIVE ack 2
|
||||
// Bob: RECEIVE ack 2 nack 1 <-- NOT bad
|
||||
|
||||
/*
|
||||
if (con.getUnackedPacketsSent() > 0) {
|
||||
// peer got a dup
|
||||
int oldSize = con.getOptions().getWindowSize();
|
||||
oldSize >>>= 1;
|
||||
if (oldSize <= 0)
|
||||
oldSize = 1;
|
||||
con.getOptions().setWindowSize(oldSize);
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("New window size " + newWindowSize + " congestionSeenAt: "
|
||||
+ con.getLastCongestionSeenAt() + " (#resends: " + numResends
|
||||
+ ") for " + con);
|
||||
con.getOptions().setWindowSize(newWindowSize);
|
||||
con.setCongestionWindowEnd(newWindowSize + lowest);
|
||||
}
|
||||
return false;
|
||||
|
||||
return congested;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -33,9 +33,9 @@ public class MessageInputStream extends InputStream {
|
||||
private List _readyDataBlocks;
|
||||
private int _readyDataBlockIndex;
|
||||
/** highest message ID used in the readyDataBlocks */
|
||||
private long _highestReadyBlockId;
|
||||
private volatile long _highestReadyBlockId;
|
||||
/** highest overall message ID */
|
||||
private long _highestBlockId;
|
||||
private volatile long _highestBlockId;
|
||||
/**
|
||||
* Message ID (Long) to ByteArray for blocks received
|
||||
* out of order when there are lower IDs not yet
|
||||
@ -74,15 +74,13 @@ public class MessageInputStream extends InputStream {
|
||||
|
||||
/** What is the highest block ID we've completely received through? */
|
||||
public long getHighestReadyBockId() {
|
||||
synchronized (_dataLock) {
|
||||
return _highestReadyBlockId;
|
||||
}
|
||||
// not synchronized as it doesnt hurt to read a too-low value
|
||||
return _highestReadyBlockId;
|
||||
}
|
||||
|
||||
public long getHighestBlockId() {
|
||||
synchronized (_dataLock) {
|
||||
return _highestBlockId;
|
||||
}
|
||||
// not synchronized as it doesnt hurt to read a too-low value
|
||||
return _highestBlockId;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -394,6 +392,21 @@ public class MessageInputStream extends InputStream {
|
||||
}
|
||||
}
|
||||
|
||||
public int getTotalReadySize() {
|
||||
synchronized (_dataLock) {
|
||||
if (_locallyClosed) return 0;
|
||||
int numBytes = 0;
|
||||
for (int i = 0; i < _readyDataBlocks.size(); i++) {
|
||||
ByteArray cur = (ByteArray)_readyDataBlocks.get(i);
|
||||
if (i == 0)
|
||||
numBytes += cur.getData().length - _readyDataBlockIndex;
|
||||
else
|
||||
numBytes += cur.getData().length;
|
||||
}
|
||||
return numBytes;
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
synchronized (_dataLock) {
|
||||
_readyDataBlocks.clear();
|
||||
|
@ -47,7 +47,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
_written = 0;
|
||||
_closed = false;
|
||||
_writeTimeout = -1;
|
||||
_passiveFlushDelay = 5*1000;
|
||||
_passiveFlushDelay = 2*1000;
|
||||
_flusher = new Flusher();
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("MessageOutputStream created");
|
||||
@ -83,8 +83,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
remaining = 0;
|
||||
_lastBuffered = _context.clock().now();
|
||||
if (_passiveFlushDelay > 0) {
|
||||
// if it is already enqueued, this just pushes it further out
|
||||
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
||||
_flusher.enqueue();
|
||||
}
|
||||
} else {
|
||||
// buffer whatever we can fit then flush,
|
||||
@ -115,9 +114,9 @@ public class MessageOutputStream extends OutputStream {
|
||||
ws.waitForAccept(_writeTimeout);
|
||||
if (!ws.writeAccepted()) {
|
||||
if (_writeTimeout > 0)
|
||||
throw new InterruptedIOException("Write not accepted within timeout");
|
||||
throw new InterruptedIOException("Write not accepted within timeout: " + ws);
|
||||
else
|
||||
throw new IOException("Write not accepted into the queue");
|
||||
throw new IOException("Write not accepted into the queue: " + ws);
|
||||
}
|
||||
} else {
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
@ -140,7 +139,24 @@ public class MessageOutputStream extends OutputStream {
|
||||
* period of inactivity
|
||||
*/
|
||||
private class Flusher implements SimpleTimer.TimedEvent {
|
||||
private boolean _enqueued;
|
||||
public void enqueue() {
|
||||
// no need to be overly worried about duplicates - it would just
|
||||
// push it further out
|
||||
if (!_enqueued)
|
||||
SimpleTimer.getInstance().addEvent(_flusher, _passiveFlushDelay);
|
||||
_enqueued = true;
|
||||
}
|
||||
public void timeReached() {
|
||||
_enqueued = false;
|
||||
long timeLeft = (_lastBuffered + _passiveFlushDelay - _context.clock().now());
|
||||
if (timeLeft > 0)
|
||||
enqueue();
|
||||
else
|
||||
doFlush();
|
||||
}
|
||||
|
||||
private void doFlush() {
|
||||
boolean sent = false;
|
||||
WriteStatus ws = null;
|
||||
synchronized (_dataLock) {
|
||||
@ -159,7 +175,6 @@ public class MessageOutputStream extends OutputStream {
|
||||
if (sent && _log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Passive flush of " + ws);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -275,6 +290,7 @@ public class MessageOutputStream extends OutputStream {
|
||||
}
|
||||
void flushAvailable(DataReceiver target, boolean blocking) throws IOException {
|
||||
WriteStatus ws = null;
|
||||
long before = System.currentTimeMillis();
|
||||
synchronized (_dataLock) {
|
||||
// _buf may be null, but the data receiver can handle that just fine,
|
||||
// deciding whether or not to send a packet
|
||||
@ -284,6 +300,10 @@ public class MessageOutputStream extends OutputStream {
|
||||
_dataLock.notifyAll();
|
||||
_lastFlushed = _context.clock().now();
|
||||
}
|
||||
long afterBuild = System.currentTimeMillis();
|
||||
if ( (afterBuild - before > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (afterBuild-before) + "ms to build a packet? " + ws);
|
||||
|
||||
if (blocking && ws != null) {
|
||||
ws.waitForAccept(_writeTimeout);
|
||||
if (ws.writeFailed())
|
||||
@ -291,6 +311,9 @@ public class MessageOutputStream extends OutputStream {
|
||||
else if (!ws.writeAccepted())
|
||||
throw new InterruptedIOException("Flush available timed out");
|
||||
}
|
||||
long afterAccept = System.currentTimeMillis();
|
||||
if ( (afterAccept - afterBuild > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (afterAccept-afterBuild) + "ms to accept a packet? " + ws);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -104,7 +104,7 @@ public class PacketHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("hh:mm:ss.SSS");
|
||||
private static final SimpleDateFormat _fmt = new SimpleDateFormat("HH:mm:ss.SSS");
|
||||
void displayPacket(Packet packet, String prefix) {
|
||||
String msg = null;
|
||||
synchronized (_fmt) {
|
||||
|
@ -5,6 +5,8 @@ import java.util.Set;
|
||||
import net.i2p.I2PAppContext;
|
||||
import net.i2p.data.Destination;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleTimer;
|
||||
|
||||
/**
|
||||
* coordinate local attributes about a packet - send time, ack time, number of
|
||||
@ -12,6 +14,7 @@ import net.i2p.data.SessionKey;
|
||||
*/
|
||||
public class PacketLocal extends Packet implements MessageOutputStream.WriteStatus {
|
||||
private I2PAppContext _context;
|
||||
private Log _log;
|
||||
private Connection _connection;
|
||||
private Destination _to;
|
||||
private SessionKey _keyUsed;
|
||||
@ -22,6 +25,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
private long _acceptedOn;
|
||||
private long _ackOn;
|
||||
private long _cancelledOn;
|
||||
private SimpleTimer.TimedEvent _resendEvent;
|
||||
|
||||
public PacketLocal(I2PAppContext ctx, Destination to) {
|
||||
this(ctx, to, null);
|
||||
@ -29,6 +33,7 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public PacketLocal(I2PAppContext ctx, Destination to, Connection con) {
|
||||
_context = ctx;
|
||||
_createdOn = ctx.clock().now();
|
||||
_log = ctx.logManager().getLog(PacketLocal.class);
|
||||
_to = to;
|
||||
_connection = con;
|
||||
_lastSend = -1;
|
||||
@ -78,12 +83,16 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
_ackOn = _context.clock().now();
|
||||
notifyAll();
|
||||
}
|
||||
SimpleTimer.getInstance().removeEvent(_resendEvent);
|
||||
}
|
||||
public void cancelled() {
|
||||
synchronized (this) {
|
||||
_cancelledOn = _context.clock().now();
|
||||
notifyAll();
|
||||
}
|
||||
SimpleTimer.getInstance().removeEvent(_resendEvent);
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Cancelled! " + toString(), new Exception("cancelled"));
|
||||
}
|
||||
|
||||
/** how long after packet creation was it acked? */
|
||||
@ -97,6 +106,8 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public long getLastSend() { return _lastSend; }
|
||||
public Connection getConnection() { return _connection; }
|
||||
|
||||
public void setResendPacketEvent(SimpleTimer.TimedEvent evt) { _resendEvent = evt; }
|
||||
|
||||
public String toString() {
|
||||
String str = super.toString();
|
||||
if (_ackOn > 0)
|
||||
@ -108,12 +119,24 @@ public class PacketLocal extends Packet implements MessageOutputStream.WriteStat
|
||||
public void waitForAccept(int maxWaitMs) {
|
||||
if (_connection == null)
|
||||
throw new IllegalStateException("Cannot wait for accept with no connection");
|
||||
long expiration = _context.clock().now()+maxWaitMs;
|
||||
long before = _context.clock().now();
|
||||
long expiration = before+maxWaitMs;
|
||||
int queued = _connection.getUnackedPacketsSent();
|
||||
int window = _connection.getOptions().getWindowSize();
|
||||
boolean accepted = _connection.packetSendChoke(maxWaitMs);
|
||||
long after = _context.clock().now();
|
||||
if (accepted)
|
||||
_acceptedOn = _context.clock().now();
|
||||
_acceptedOn = after;
|
||||
else
|
||||
_acceptedOn = -1;
|
||||
int afterQueued = _connection.getUnackedPacketsSent();
|
||||
if ( (after - before > 1000) && (_log.shouldLog(Log.DEBUG)) )
|
||||
_log.debug("Took " + (after-before) + "ms to get "
|
||||
+ (accepted ? " accepted" : " rejected")
|
||||
+ (_cancelledOn > 0 ? " and CANCELLED" : "")
|
||||
+ ", queued behind " + queued +" with a window size of " + window
|
||||
+ ", finally accepted with " + afterQueued + " queued: "
|
||||
+ toString());
|
||||
}
|
||||
|
||||
public void waitForCompletion(int maxWaitMs) {
|
||||
|
@ -24,24 +24,17 @@ class PacketQueue {
|
||||
private Log _log;
|
||||
private I2PSession _session;
|
||||
private ConnectionManager _connectionManager;
|
||||
private byte _buf[];
|
||||
private ByteCache _cache = ByteCache.getInstance(64, 36*1024);
|
||||
|
||||
public PacketQueue(I2PAppContext context, I2PSession session, ConnectionManager mgr) {
|
||||
_context = context;
|
||||
_session = session;
|
||||
_connectionManager = mgr;
|
||||
_buf = _cache.acquire().getData(); // new byte[36*1024];
|
||||
_log = context.logManager().getLog(PacketQueue.class);
|
||||
_context.statManager().createRateStat("stream.con.sendMessageSize", "Size of a message sent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
_context.statManager().createRateStat("stream.con.sendDuplicateSize", "Size of a message resent on a connection", "Stream", new long[] { 60*1000, 10*60*1000, 60*60*1000 });
|
||||
}
|
||||
|
||||
protected void finalize() throws Throwable {
|
||||
_cache.release(new ByteArray(_buf));
|
||||
super.finalize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new packet to be sent out ASAP
|
||||
*/
|
||||
@ -53,7 +46,7 @@ class PacketQueue {
|
||||
keyUsed = new SessionKey();
|
||||
Set tagsSent = packet.getTagsSent();
|
||||
if (tagsSent == null)
|
||||
tagsSent = new HashSet();
|
||||
tagsSent = new HashSet(0);
|
||||
|
||||
// cache this from before sendMessage
|
||||
String conStr = (packet.getConnection() != null ? packet.getConnection().toString() : "");
|
||||
@ -63,29 +56,36 @@ class PacketQueue {
|
||||
} else {
|
||||
_log.debug("Sending... " + packet);
|
||||
}
|
||||
|
||||
ByteArray ba = _cache.acquire();
|
||||
byte buf[] = ba.getData();
|
||||
|
||||
long begin = 0;
|
||||
long end = 0;
|
||||
boolean sent = false;
|
||||
try {
|
||||
int size = 0;
|
||||
synchronized (this) {
|
||||
Arrays.fill(_buf, (byte)0x0);
|
||||
if (packet.shouldSign())
|
||||
size = packet.writeSignedPacket(_buf, 0, _context, _session.getPrivateKey());
|
||||
else
|
||||
size = packet.writePacket(_buf, 0);
|
||||
long beforeWrite = System.currentTimeMillis();
|
||||
if (packet.shouldSign())
|
||||
size = packet.writeSignedPacket(buf, 0, _context, _session.getPrivateKey());
|
||||
else
|
||||
size = packet.writePacket(buf, 0);
|
||||
long writeTime = System.currentTimeMillis() - beforeWrite;
|
||||
if ( (writeTime > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("took " + writeTime + "ms to write the packet: " + packet);
|
||||
|
||||
// this should not block!
|
||||
begin = _context.clock().now();
|
||||
sent = _session.sendMessage(packet.getTo(), _buf, 0, size, keyUsed, tagsSent);
|
||||
end = _context.clock().now();
|
||||
}
|
||||
// this should not block!
|
||||
begin = _context.clock().now();
|
||||
sent = _session.sendMessage(packet.getTo(), buf, 0, size, keyUsed, tagsSent);
|
||||
end = _context.clock().now();
|
||||
|
||||
if ( (end-begin > 1000) && (_log.shouldLog(Log.WARN)) )
|
||||
_log.warn("Took " + (end-begin) + "ms to sendMessage(...) " + packet);
|
||||
|
||||
_context.statManager().addRateData("stream.con.sendMessageSize", size, packet.getLifetime());
|
||||
if (packet.getNumSends() > 1)
|
||||
_context.statManager().addRateData("stream.con.sendDuplicateSize", size, packet.getLifetime());
|
||||
|
||||
|
||||
Connection con = packet.getConnection();
|
||||
if (con != null) {
|
||||
con.incrementBytesSent(size);
|
||||
@ -97,6 +97,8 @@ class PacketQueue {
|
||||
_log.warn("Unable to send the packet " + packet, ise);
|
||||
}
|
||||
|
||||
_cache.release(ba);
|
||||
|
||||
if (!sent) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Send failed for " + packet);
|
||||
|
@ -32,5 +32,6 @@ abstract class SchedulerImpl implements TaskScheduler {
|
||||
// _log.debug("firing event on " + _connection, _addedBy);
|
||||
_connection.eventOccurred();
|
||||
}
|
||||
public String toString() { return "event on " + _connection; }
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user